diff --git a/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-branch-validation.txt b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-branch-validation.txt new file mode 100644 index 000000000..2fee46779 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-branch-validation.txt @@ -0,0 +1,6 @@ +Branch Validation: PASS + +PASS - Current branch is pr/26174-ALFA-014-game-hub-parent-columns-center. +PASS - Expected branch is pr/26174-ALFA-014-game-hub-parent-columns-center. +PASS - Branch stacks on PR_26174_ALFA_013-game-hub-game-row-child-rows. +PASS - Work stayed scoped to Game Hub centered parent-table layout, impacted tests, and required reports. diff --git a/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-manual-validation-notes.txt b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-manual-validation-notes.txt new file mode 100644 index 000000000..9c1dfe458 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-manual-validation-notes.txt @@ -0,0 +1,9 @@ +Manual Validation Notes: PASS + +- Reviewed table_first_ui.md and kept the Idea Board-style parent row plus expanded child row model. +- Confirmed the game table renders in the center panel instead of the left tool column. +- Confirmed the old Project Information table/card is absent. +- Confirmed parent headers are exactly Game, Purpose, Status, Owner, Actions. +- Confirmed Role and Next Tool are absent from the parent row. +- Confirmed Source Idea and Readiness Output remain child tables under expanded game rows. +- Confirmed safe empty/unavailable states still pass targeted validation. diff --git a/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-requirement-checklist.txt b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-requirement-checklist.txt new file mode 100644 index 000000000..5444b855b --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-requirement-checklist.txt @@ -0,0 +1,13 @@ +Requirement Checklist: PASS + +PASS - Parent table columns are Game, Purpose, Status, Owner, Actions. +PASS - Old Project Information layout was removed. +PASS - Role is not shown in the parent row. +PASS - Next Tool is not shown in the parent row. +PASS - Each game remains the parent row. +PASS - Source Idea remains a child row/table under the game row. +PASS - Readiness Output remains a child row/table under the game row. +PASS - Existing API/service contract was preserved. +PASS - No browser-owned project data was introduced. +PASS - No new readiness math was added. +PASS - table_first_ui.md examples were followed by keeping the parent-row plus expanded child-row table pattern. diff --git a/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-validation-lane.txt b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-validation-lane.txt new file mode 100644 index 000000000..6f7b60e48 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-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 preserves guest browsing and blocks guest saves|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state|Game Hub displays and edits game purpose and member role|Game Hub readiness child rows update from mock game state" +Result: 7 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 -- 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_014-game-hub-parent-columns-center.md b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center.md new file mode 100644 index 000000000..0cf3a5fe1 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center.md @@ -0,0 +1,19 @@ +# PR_26174_ALFA_014-game-hub-parent-columns-center + +## Purpose + +Update Game Hub parent table columns to the approved centered parent-row structure. + +## Summary + +- Moved the Game Hub parent table into the center panel. +- Removed the old Project Information table/card layout and its Role/Next Tool display columns. +- Kept each game as a parent row with Game, Purpose, Status, Owner, and Actions columns. +- Preserved Source Idea and Readiness Output as child row tables under the game parent row. +- Preserved the existing Game Hub API/service contract and readiness output model. + +## 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 preserves guest browsing and blocks guest saves|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state|Game Hub displays and edits game purpose and member role|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 -- 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 34bd72f5e..de3b0987b 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,12 +1,11 @@ -docs_build/dev/ProjectInstructions/addendums/table_first_ui.md 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 -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 +docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center.md +docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-branch-validation.txt +docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-requirement-checklist.txt +docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-validation-lane.txt +docs_build/dev/reports/PR_26174_ALFA_014-game-hub-parent-columns-center-manual-validation-notes.txt diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 714f931d2..078255e32 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,539 +1,351 @@ -diff --git a/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md b/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md -index 23ceb1622..5645ebcbb 100644 ---- a/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md -+++ b/docs_build/dev/ProjectInstructions/addendums/table_first_ui.md -@@ -15,3 +15,69 @@ Avoid: - - Reference implementation: - Idea Board is the reference implementation. -+ -+ -+DB base -+Creator Table 1 -+Parent Table *-1 user -+Child Table -- *-1 Parent -+ -+ -+No selected Items -+┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐ -+│ Idea Board │ -+├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ -+│ Idea │ Pitch │ Status │ Updated │ Notes │ Actions │ -+├───────────────┼─────────────────────────────────────────┼───────────┼────────────┼─────────┼─────────┤ -+│ Top Thougts │ Smartest person wins... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ -+├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ -+│ Sky Orchard │ Grow floating islands... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ -+├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ -+│ Clockwork... │ Deliver messages through looping city...│ New │ 2026-06-20 │ 0 Notes │ Edit Del│ -+├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ -+│ [ Add Idea ] │ -+└──────────────────────────────────────────────────────────────────────────────────────────────────────┘ -+ -+ -+Clicking {Sky Orchard {chevron down arrow}] Expands/Acording the Note(s) for that Idea -+┌────────────────────────────────────────────────────────────────────────────────────────────────────┐ -+│ Idea Board │ -+├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ -+│ Idea │ Pitch │ Status │ Updated │ Notes │ Actions │ -+├───────────────┼─────────────────────────────────────────┼───────────┼────────────┼─────────┼─────────┤ -+│ Top Thougts │ Smartest person wins... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ -+├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ -+│ Sky Orchard[^}│ Grow floating islands... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ -+├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ -+│ Notes │ -+│ --------------------------------------------------------------------------------------------------- │ -+│ note 1 [Edit] [Delete] │ -+│ System seed note: compare early ideas before project creation. [Edit] │ -+│ Ask whether the core loop is planning, defense, or both. [Edit] [Delete] │ -+│ │ -+│ [ Add Note ] │ -+├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ -+│ Clockwork... │ Deliver messages through looping city...│ New │ 2026-06-20 │ 0 Notes │ Edit Del│ -+├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ -+│ [ Add Idea ] │ -+└──────────────────────────────────────────────────────────────────────────────────────────────────────┘ -+ -+Clickin Add Idea -+┌────────────────────────────────────────────────────────────────────────────────────────────────────┐ -+│ Idea Board │ -+├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ -+│ Idea │ Pitch │ Status │ Updated │ Notes │ Actions │ -+├───────────────┼─────────────────────────────────────────┼───────────┼────────────┼─────────┼─────────┤ -+│ Top Thougts │ Smartest person wins... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ -+├───────────────┬─────────────────────────────────────────┬───────────┬────────────┬─────────┬─────────┤ -+│ Sky Orchard │ Grow floating islands... │ Exploring │ 2026-06-20 │ 3 Notes │ Edit Del│ -+├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ -+│ Clockwork... │ Deliver messages through looping city...│ New │ 2026-06-20 │ 0 Notes │ Edit Del│ -+├───────────────┴─────────────────────────────────────────┴───────────┴────────────┴─────────┴─────────┤ -+│ [input.....] │ [input.....] │ [Dropdown]│ [autofile] │ 0 Notes │ Save Can│ -+└──────────────────────────────────────────────────────────────────────────────────────────────────────┘ -+ -+ -+ -+ -+ diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs -index 4e617ed35..ae1de99c4 100644 +index ae1de99c4..c4bceb838 100644 --- a/tests/playwright/tools/GameHubMockRepository.spec.mjs +++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs -@@ -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,11 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { +@@ -253,15 +253,17 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { + await expect(page.getByRole("button", { name: "Create Game" })).toBeEnabled(); + await expect(page.getByRole("button", { name: "Delete Open Game" })).toHaveClass("btn"); + await expect(page.getByRole("button", { name: "Delete Open Game" })).toBeEnabled(); +- 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-project-record-status]")).toHaveText("Game table loaded."); ++ await expect(page.locator("[data-game-project-information]")).toHaveCount(0); ++ await expect(page.locator("[data-project-records-table]")).toHaveCount(0); ++ await expect(page.locator("[data-active-game-name]")).toHaveCount(0); ++ await expect(page.locator("[data-current-user-role]")).toHaveCount(0); ++ await expect(page.locator("[data-recommended-next-tool]")).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.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"); ++ await expect(page.locator("aside [data-game-list]")).toHaveCount(0); ++ await expect(page.locator(".tool-center-panel [data-game-list]")).toContainText("Demo Game"); + await expect(page.locator("[data-game-list]")).toContainText("Demo Game"); 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("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); -+ await expect(page.locator("[data-game-rows-table='true'] thead th")).toHaveText([ - "Game", - "Purpose", - "Status", -@@ -280,17 +284,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 +336,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { - } - }); +@@ -277,7 +279,9 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { + "Owner", + "Actions", + ]); ++ await expect(page.locator("[data-game-rows-table='true'] thead")).not.toContainText(/Role|Next Tool/); + const demoGameRow = page.locator("[data-game-row='demo-game']"); ++ await expect(demoGameRow.locator("td")).toHaveText(["Game", "Under Construction", "User 1", "Open Demo Game (Active)"]); + await expect(demoGameRow.locator("> .status")).toHaveCount(0); + await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "false"); + await expect(demoGameRow.getByRole("button", { name: "Open Demo Game (Active)" })).toHaveClass(/primary/); +@@ -310,23 +314,20 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { + + await page.getByLabel("Game Name").fill("Launch Test Game"); + await page.getByRole("button", { name: "Create Game" }).click(); +- await expect(page.locator("[data-active-game-name]")).toHaveText("Launch Test Game"); + await expect(page.locator("[data-game-list]")).toContainText("Launch Test Game"); +- await expect(page.locator("[data-game-project-information]")).toContainText("Launch Test Game"); + await expect(page.locator("[data-game-row='launch-test-game-1']").getByRole("button", { name: "Open Launch Test Game (Active)" })).toHaveClass(/primary/); + await expect(page.locator("[data-game-hub-log]")).toHaveText("Created and opened Launch Test Game."); + + await page.getByLabel("Game Name").fill("Archive Game"); + await page.getByRole("button", { name: "Create Game" }).click(); +- await expect(page.locator("[data-active-game-name]")).toHaveText("Archive Game"); ++ await expect(page.locator("[data-game-row='archive-game-2']").getByRole("button", { name: "Open Archive Game (Active)" })).toHaveClass(/primary/); + + await page.getByRole("button", { name: "Open Launch Test Game" }).click(); +- await expect(page.locator("[data-active-game-name]")).toHaveText("Launch Test Game"); + await expect(page.locator("[data-game-row='launch-test-game-1']").getByRole("button", { name: "Open Launch Test Game (Active)" })).toHaveAttribute("data-game-active", "true"); + await expect(page.locator("[data-game-hub-log]")).toHaveText("Opened Launch Test Game."); + + await page.getByRole("button", { name: "Delete Open Game" }).click(); +- await expect(page.locator("[data-active-game-name]")).not.toHaveText("Launch Test Game"); ++ await expect(page.locator("[data-game-row='launch-test-game-1']")).toHaveCount(0); + await expect(page.locator("[data-game-list]")).not.toContainText("Launch Test Game"); + await expect(page.locator("[data-game-hub-log]")).toHaveText("Deleted Launch Test Game."); + +@@ -471,10 +472,10 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page } + const failures = await openRepoPage(page, "/toolbox/game-hub/index.html"); + + try { +- await expect(page.locator("[data-active-game-name]")).toHaveText("Demo Game"); ++ await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Open Demo Game (Active)" })).toHaveClass(/primary/); + await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo"); +- await expect(page.locator("[data-project-record-status]")).toHaveText("Project Information loaded. Sign in to save changes."); +- await expect(page.locator("[data-project-records-table]")).toContainText("Demo Game"); ++ await expect(page.locator("[data-project-record-status]")).toHaveText("Game table loaded. Sign in to save changes."); ++ await expect(page.locator("[data-project-records-table]")).toHaveCount(0); + await expect(page.getByRole("button", { name: "Create Game" })).toBeDisabled(); + await expect(page.getByRole("button", { name: "Delete Open Game" })).toBeDisabled(); + await expect(page.getByLabel("Game Name")).toBeDisabled(); +@@ -483,7 +484,7 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page } + await expect(page.getByLabel("Current User Role")).toBeDisabled(); --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 +423,11 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = + await page.getByRole("button", { name: "Open Gravity Demo" }).click(); +- await expect(page.locator("[data-active-game-name]")).toHaveText("Gravity Demo"); ++ await expect(page.locator("[data-game-row='gravity-demo']").getByRole("button", { name: "Open Gravity Demo (Active)" })).toHaveClass(/primary/); + await expect(page.locator("[data-game-hub-log]")).toHaveText("Sign in to create or update Game Hub projects."); + + await expectNoPageFailures(failures); +@@ -538,7 +539,7 @@ test("Game Hub shows a creator-safe empty state when no projects exist", async ( 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("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); -+ 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 +435,14 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = +- await expect(page.locator("[data-active-game-name]")).toHaveText("No game open"); ++ await expect(page.locator("[data-active-game-name]")).toHaveCount(0); + await expect(page.locator("[data-game-list] [data-game-list-status='empty']")).toHaveText("No Game Hub projects yet. Create a game to start building."); + await expect(page.locator("[data-game-list] [data-game-row]")).toHaveCount(0); + await expect(page.locator("[data-game-hub-log]")).not.toContainText(/server|API|repository|database|stack|error/i); +@@ -591,7 +592,7 @@ test("Game Hub shows active-game errors without throwing", 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 +453,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/); + try { + expect(failures.failedRequests.some((request) => request.includes("502") && request.includes("/methods/getActiveGame"))).toBe(true); +- await expect(page.locator("[data-active-game-name]")).toHaveText("No game open"); ++ await expect(page.locator("[data-active-game-name]")).toHaveCount(0); + await expect(page.locator("[data-game-hub-log]")).toContainText("Active game is temporarily unavailable."); + expect(failures.pageErrors).toEqual([]); + expect(failures.consoleErrors.filter((message) => !message.includes("status of 502"))).toEqual([]); +@@ -620,8 +621,8 @@ test("Game Hub reports malformed active-game payloads without throwing", async ( + const failures = await openRepoPage(page, "/toolbox/game-hub/index.html"); -- 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/); -@@ -681,46 +688,43 @@ test("Game Hub displays and edits game purpose and member role", async ({ page } - } - }); + try { +- await expect(page.locator("[data-active-game-name]")).toHaveText("No game open"); +- await expect(page.locator("[data-current-user-role]")).toHaveText("Viewer"); ++ await expect(page.locator("[data-active-game-name]")).toHaveCount(0); ++ await expect(page.locator("[data-current-user-role]")).toHaveCount(0); + await expect(page.locator("[data-game-hub-log]")).toContainText("Active game is temporarily unavailable."); + await expect(page.getByLabel("Game Purpose")).toBeDisabled(); + +@@ -663,23 +664,23 @@ test("Game Hub displays and edits game purpose and member role", async ({ page } + await expect(page.getByLabel("Current User Role")).toHaveValue("Owner"); --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 }) => { + await page.getByLabel("Game Purpose").selectOption("Learning Game"); +- await expect(page.locator("[data-active-game-purpose]")).toHaveText("Learning Game"); ++ await expect(page.locator("[data-game-row='demo-game'] td").nth(0)).toHaveText("Learning Game"); + await expect(page.locator("[data-game-hub-log]")).toHaveText("Updated Demo Game purpose to Learning Game."); + + await page.getByLabel("Game Status").selectOption("Ready for Testing"); +- await expect(page.locator("[data-active-game-status]")).toHaveText("Ready for Testing"); ++ await expect(page.locator("[data-game-row='demo-game'] td").nth(1)).toHaveText("Ready for Testing"); + await expect(page.locator("[data-game-hub-log]")).toHaveText("Updated Demo Game status to Ready for Testing."); + + await page.getByLabel("Current User Role").selectOption("Designer"); +- await expect(page.locator("[data-current-user-role]")).toHaveText("Designer"); ++ await expect(page.getByLabel("Current User Role")).toHaveValue("Designer"); + await expect(page.locator("[data-game-hub-log]")).toHaveText("Updated current user role to Designer."); + + await page.getByLabel("Game Purpose").selectOption("Capability Demo"); + await page.getByLabel("Game Name").fill("Purpose Review Game"); + await page.getByRole("button", { name: "Create Game" }).click(); +- await expect(page.locator("[data-active-game-name]")).toHaveText("Purpose Review Game"); +- await expect(page.locator("[data-active-game-purpose]")).toHaveText("Capability Demo"); +- await expect(page.locator("[data-current-user-role]")).toHaveText("Owner"); ++ await expect(page.locator("[data-game-row='purpose-review-game-1']").getByRole("button", { name: "Open Purpose Review Game (Active)" })).toHaveClass(/primary/); ++ await expect(page.locator("[data-game-row='purpose-review-game-1'] td").nth(0)).toHaveText("Capability Demo"); ++ await expect(page.getByLabel("Current User Role")).toHaveValue("Owner"); + await expect(page.locator("[data-game-list]")).toContainText("Purpose Review Game"); + + await expectNoPageFailures(failures); +@@ -692,7 +693,7 @@ 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 expect(page.locator("[data-recommended-next-tool]").first()).toHaveText("Game Configuration"); ++ await expect(page.locator("[data-recommended-next-tool]")).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); +@@ -714,14 +715,15 @@ test("Game Hub readiness child rows update from mock game state", async ({ page 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 expect(page.locator("[data-game-project-information]")).toContainText("Progress Review Game"); ++ await expect(page.locator("[data-game-project-information]")).toHaveCount(0); ++ await expect(page.locator("[data-game-list]")).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 { +- await expect(page.locator("[data-active-game-name]")).toHaveText("Demo Game"); ++ await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Open Demo Game (Active)" })).toHaveClass(/primary/); + 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"); diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -index f861f1f44..2c7b70b5d 100644 +index 2c7b70b5d..c7bfbe90c 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 }) => { +@@ -385,8 +385,10 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { + await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='open-project']").click(); + await page.waitForURL(/\/toolbox\/game-hub\/index\.html\?game=lantern-reef-\d+$/); 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-active-game-name]")).toHaveText("Lantern Reef"); ++ await expect(page.locator("[data-active-game-name]")).toHaveCount(0); 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(page.locator("aside [data-game-list]")).toHaveCount(0); ++ await expect(page.locator(".tool-center-panel [data-game-list]")).toContainText("Lantern Reef"); + 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); +@@ -409,7 +411,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { 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 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-active-game-name]")).toHaveText("Lantern Reef"); ++ await expect(page.locator("[data-active-game-name]")).toHaveCount(0); 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(); + await expect(page.locator("[data-source-idea-section]")).toHaveCount(0); + await expect(page.locator("[data-game-output-panels]")).toHaveCount(0); diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js -index 4b21814d7..029936527 100644 +index 029936527..ad2eee2f4 100644 --- a/toolbox/game-hub/game-hub.js +++ b/toolbox/game-hub/game-hub.js -@@ -27,10 +27,6 @@ const elements = { +@@ -9,12 +9,6 @@ import { getSessionCurrent } from "../../src/api/session-api-client.js"; + const repository = createGameHubApiRepository(); + + const elements = { +- activeGameName: document.querySelector("[data-active-game-name]"), +- activeGameOwner: document.querySelector("[data-active-game-owner]"), +- activeGamePurpose: document.querySelector("[data-active-game-purpose]"), +- activeGameStatus: document.querySelector("[data-active-game-status]"), +- currentFocus: document.querySelector("[data-current-focus]"), +- currentUserRole: document.querySelector("[data-current-user-role]"), + currentUserRoleInput: document.querySelector("[data-current-user-role-input]"), + deleteOpenGame: document.querySelector("[data-game-delete-active]"), + form: document.querySelector("[data-game-form]"), +@@ -22,15 +16,10 @@ const elements = { + nameInput: document.querySelector("[data-game-name-input]"), + progressChecklist: document.querySelector("[data-game-progress-checklist]"), + gameList: document.querySelector("[data-game-list]"), +- gameProgress: document.querySelector("[data-game-progress]"), + gameJourneyLink: document.querySelector("[data-game-journey-link]"), projectRecordStatus: document.querySelector("[data-project-record-status]"), - projectRecordsTable: document.querySelector("[data-project-records-table]"), +- 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]"), +- 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)); -- 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; +- publishingProgress: document.querySelector("[data-publishing-progress]"), +- recommendedNextTool: document.querySelectorAll("[data-recommended-next-tool]"), + statusLog: document.querySelector("[data-game-hub-log]"), + tableCounts: document.querySelector("[data-game-table-counts]"), + }; +@@ -390,31 +379,10 @@ function renderGameParentRow(tbody, game, activeGame, progress) { + } } --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 = "
Review the open project and its source idea.
-+Review the open project, then expand its game row to see Source Idea and Readiness Output.
-Review the open project, then expand its game row to see Source Idea and Readiness Output.
+-| Idea | No source idea yet | ||||
|---|---|---|---|---|---|
| Pitch | Create a project from Idea Board to see source details. | ||||
| Notes |
|
| Project | +-Status | +-Purpose | +-Owner | +-Role | +-Next Tool | +-
|---|---|---|---|---|---|
| Demo Game | +-Under Construction | +-Game | +-No owner | +-Owner | +-Game Configuration | +-
Under Construction
Demo Game identity ready
Publish blocked until configuration and required assets are ready
Complete Game Configuration
Game Configuration
| Path | Status | Next Tool |
|---|---|---|
| Plan | Under Construction | Game Configuration |
| Configure | Planned | Build Game |
| Release | Planned | Publish |
Review games in the parent table, then expand a game row to see Source Idea and Readiness Output.
++Review the open project, then expand its game row to see Source Idea and Readiness Output.
-| Project | -Status | -Purpose | -Owner | -Role | -Next Tool | -
|---|---|---|---|---|---|
| Demo Game | -Under Construction | -Game | -No owner | -Owner | -Game Configuration | -
Review games in the parent table, then expand a game row to see Source Idea and Readiness Output.
+