Skip to content

Commit eb693c1

Browse files
committed
Merge PR_26177_ALFA_059-game-crew-foundation
2 parents ada9adb + 59008e8 commit eb693c1

18 files changed

Lines changed: 1106 additions & 205 deletions
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { getSessionCurrent } from "../../../../src/api/session-api-client.js";
2+
import {
3+
createServerRepositoryClient,
4+
readServerToolConstants,
5+
requireServerConstant,
6+
} from "../../../../src/api/server-api-client.js";
7+
8+
const constants = readServerToolConstants("game-crew");
9+
10+
export const GAME_CREW_TABLES = Object.freeze(requireServerConstant(constants, "GAME_CREW_TABLES", "game-crew"));
11+
export const GAME_CREW_MEMBER_ROLES = Object.freeze(requireServerConstant(constants, "GAME_CREW_MEMBER_ROLES", "game-crew"));
12+
13+
export function createGameCrewApiRepository(options = {}) {
14+
return createServerRepositoryClient("game-crew", options);
15+
}
16+
17+
const repository = createGameCrewApiRepository();
18+
19+
const elements = {
20+
action: document.querySelector("[data-game-crew-action]"),
21+
add: document.querySelector("[data-game-crew-add]"),
22+
count: document.querySelector("[data-game-crew-count]"),
23+
guidance: document.querySelector("[data-game-crew-guidance]"),
24+
log: document.querySelector("[data-game-crew-log]"),
25+
outputStatus: document.querySelector("[data-game-crew-output-status]"),
26+
owner: document.querySelector("[data-game-crew-owner]"),
27+
projectName: document.querySelector("[data-game-crew-project-name]"),
28+
refresh: document.querySelector("[data-game-crew-refresh]"),
29+
selected: document.querySelector("[data-game-crew-selected]"),
30+
status: document.querySelector("[data-game-crew-status]"),
31+
table: document.querySelector("[data-game-crew-table]"),
32+
tableCounts: document.querySelector("[data-game-crew-table-counts]"),
33+
};
34+
35+
function setText(target, value) {
36+
if (target) {
37+
target.textContent = value;
38+
}
39+
}
40+
41+
function createCell(text) {
42+
const cell = document.createElement("td");
43+
cell.textContent = text;
44+
return cell;
45+
}
46+
47+
function createButton(label, datasetName, value, options = {}) {
48+
const button = document.createElement("button");
49+
button.className = options.secondary ? "btn btn--secondary btn--compact" : "btn btn--compact";
50+
button.type = "button";
51+
button.dataset[datasetName] = value;
52+
button.textContent = label;
53+
if (options.disabled) {
54+
button.disabled = true;
55+
}
56+
return button;
57+
}
58+
59+
function currentSession() {
60+
try {
61+
return getSessionCurrent();
62+
} catch {
63+
return { authenticated: false };
64+
}
65+
}
66+
67+
function redirectGuestWriteAction() {
68+
if (currentSession()?.authenticated === true) {
69+
return false;
70+
}
71+
setText(elements.log, "Sign in before changing project crew membership.");
72+
window.location.href = new URL("/account/sign-in.html", window.location.href).href;
73+
return true;
74+
}
75+
76+
function renderTableCounts(snapshot) {
77+
if (!elements.tableCounts) {
78+
return;
79+
}
80+
elements.tableCounts.replaceChildren();
81+
snapshot.tableCounts.forEach((count) => {
82+
const row = document.createElement("tr");
83+
row.append(createCell(count.table), createCell(String(count.rows)));
84+
elements.tableCounts.append(row);
85+
});
86+
}
87+
88+
function renderMembers(snapshot) {
89+
if (!elements.table) {
90+
return;
91+
}
92+
elements.table.replaceChildren();
93+
94+
if (!snapshot.members.length) {
95+
const row = document.createElement("tr");
96+
const cell = document.createElement("td");
97+
cell.colSpan = 5;
98+
cell.textContent = "No crew members yet.";
99+
row.append(cell);
100+
elements.table.append(row);
101+
return;
102+
}
103+
104+
snapshot.members.forEach((member) => {
105+
const row = document.createElement("tr");
106+
row.dataset.gameCrewMemberRow = member.userKey;
107+
const actions = document.createElement("div");
108+
actions.className = "action-group action-group--tight";
109+
actions.append(createButton(
110+
member.role === "Owner" ? "Owner locked" : "Remove",
111+
"gameCrewRemove",
112+
member.userKey,
113+
{ disabled: member.role === "Owner", secondary: true },
114+
));
115+
const actionCell = document.createElement("td");
116+
actionCell.append(actions);
117+
row.append(
118+
createCell(member.displayName),
119+
createCell(member.role),
120+
createCell(member.status),
121+
createCell(member.joinedAt ? new Date(member.joinedAt).toLocaleDateString() : "Ready"),
122+
actionCell,
123+
);
124+
elements.table.append(row);
125+
});
126+
}
127+
128+
function normalizeSnapshot(value) {
129+
return value && typeof value === "object" && Array.isArray(value.members)
130+
? value
131+
: {
132+
activeProject: null,
133+
guidance: "Project crew is temporarily unavailable. Refresh the page.",
134+
members: [],
135+
owner: null,
136+
status: "Unavailable",
137+
tableCounts: GAME_CREW_TABLES.map((table) => ({ rows: 0, table })),
138+
};
139+
}
140+
141+
function render() {
142+
const snapshot = normalizeSnapshot(repository.getSnapshot());
143+
setText(elements.status, snapshot.status);
144+
setText(elements.outputStatus, snapshot.status);
145+
setText(elements.owner, snapshot.owner?.displayName || snapshot.activeProject?.ownerDisplayName || "Unknown");
146+
setText(elements.count, String(snapshot.members.length));
147+
setText(elements.projectName, snapshot.activeProject?.name || "No project selected");
148+
setText(elements.guidance, snapshot.guidance);
149+
renderMembers(snapshot);
150+
renderTableCounts(snapshot);
151+
}
152+
153+
elements.add?.addEventListener("click", () => {
154+
if (redirectGuestWriteAction()) {
155+
return;
156+
}
157+
const result = repository.addMember();
158+
setText(elements.action, result.message);
159+
setText(elements.log, result.message);
160+
render();
161+
});
162+
163+
elements.refresh?.addEventListener("click", () => {
164+
setText(elements.log, "Project crew refreshed from the API.");
165+
render();
166+
});
167+
168+
elements.table?.addEventListener("click", (event) => {
169+
const remove = event.target.closest("[data-game-crew-remove]");
170+
if (!remove || remove.disabled) {
171+
return;
172+
}
173+
if (redirectGuestWriteAction()) {
174+
return;
175+
}
176+
const result = repository.removeMember(remove.dataset.gameCrewRemove);
177+
setText(elements.selected, result.member?.displayName || "Selected member");
178+
setText(elements.action, result.message);
179+
setText(elements.log, result.message);
180+
render();
181+
});
182+
183+
render();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-- Game Foundry Studio DEV database DDL
2+
-- Group: Game Crew
3+
-- Ownership: docs_build/database/ddl/game-crew.sql
4+
-- Target DEV database: gamefoundry_dev
5+
-- Scope: executable grouped table DDL for active API/database migration.
6+
-- Authoritative key values are generated by the server/API layer.
7+
-- Owned tables: project_members
8+
9+
CREATE TABLE IF NOT EXISTS project_members (
10+
key text PRIMARY KEY,
11+
"projectKey" text NOT NULL REFERENCES game_workspace_games(key),
12+
"userKey" text NOT NULL REFERENCES users(key),
13+
role text NOT NULL,
14+
status text NOT NULL,
15+
"invitedBy" text REFERENCES users(key),
16+
"invitedAt" timestamptz,
17+
"joinedAt" timestamptz,
18+
"removedAt" timestamptz,
19+
"createdAt" timestamptz NOT NULL DEFAULT now(),
20+
"updatedAt" timestamptz NOT NULL DEFAULT now(),
21+
"createdBy" text NOT NULL REFERENCES users(key),
22+
"updatedBy" text NOT NULL REFERENCES users(key)
23+
);
24+
25+
CREATE UNIQUE INDEX IF NOT EXISTS idx_project_members_project_user ON project_members ("projectKey", "userKey");
26+
CREATE INDEX IF NOT EXISTS idx_project_members_projectkey ON project_members ("projectKey");
27+
CREATE INDEX IF NOT EXISTS idx_project_members_userkey ON project_members ("userKey");
28+
CREATE INDEX IF NOT EXISTS idx_project_members_createdby ON project_members ("createdBy");
29+
CREATE INDEX IF NOT EXISTS idx_project_members_updatedby ON project_members ("updatedBy");
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Game Foundry Studio DEV database DML / seed review
2+
-- Group: Game Crew
3+
-- Ownership: docs_build/database/dml/game-crew.sql
4+
-- Runtime setup/seed operations for this group must run through server-side APIs.
5+
-- Browser pages must not directly seed authoritative DB records.
6+
-- Owned tables: project_members
7+
8+
-- DML status: Server-seed-owned.
9+
-- The server/API layer generates all non-user keys and audit fields.
10+
-- Direct SQL setup for this group remains deferred until a later migration-runner PR explicitly owns it.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"group": "Game Crew",
3+
"groupKey": "game-crew",
4+
"owner": "docs_build/database/seed",
5+
"serverSideSeedRequired": true,
6+
"browserAuthoritativeKeyGenerationAllowed": false,
7+
"tables": {
8+
"project_members": []
9+
},
10+
"note": "Project crew records are owned by the API/database contract. Browser pages must not seed or generate authoritative membership records."
11+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"group": "Game Crew",
3+
"groupKey": "game-crew",
4+
"owner": "docs_build/database/seed/guest",
5+
"readOnly": true,
6+
"writableByGuest": false,
7+
"signInRedirect": "account/sign-in.html",
8+
"tables": {
9+
"project_members": []
10+
},
11+
"samplePackages": [
12+
{
13+
"key": "guest-game-crew-starter",
14+
"audience": "guest",
15+
"createdBy": "01K2GFSJ0Y0000000000000054",
16+
"groupKey": "game-crew",
17+
"label": "Guest Game Crew starter",
18+
"loadablePath": "toolbox/game-crew/index.html",
19+
"readOnly": true,
20+
"sampleKind": "toolSeed",
21+
"signInRedirect": "account/sign-in.html",
22+
"source": "docs_build/database/seed/guest/game-crew.json",
23+
"toolKey": "game-crew",
24+
"toolName": "Game Crew",
25+
"writableByGuest": false
26+
}
27+
]
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# PR_26177_ALFA_059 Branch Validation
2+
3+
- Branch: PR_26177_ALFA_059-game-crew-foundation
4+
- Runtime architecture: Browser -> API -> Database
5+
- Game Crew API/DB validation: PASS
6+
- Retired Alfa mock repository files absent: PASS
7+
- Flat Tags schema/provider alignment: PASS
8+
- Mock-db/Admin viewer expansion removed from branch delta: PASS
9+
- EOD rerun validation: PASS
10+
- Worktree validation before package: PASS pending final status check
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# PR_26177_ALFA_059 Manual Validation Notes
2+
3+
- Game Crew remains a testable foundation tool: it loads the active game context, owner, and member list.
4+
- Add/remove member actions use API/DB behavior and persist across refresh in the focused validation.
5+
- Guest member actions redirect to `account/sign-in.html`; direct API writes return 401.
6+
- Tags support add, assign to current game, remove from current game, refresh/reload persistence, edit, delete, and guest redirect on this branch.
7+
- Active runtime no longer imports retired Tags/Game Design/Game Configuration mock repository files.
8+
- The PR no longer changes `mock-db-store.js` or Admin DB viewer table wiring for the new Alfa tools.
9+
- EOD rerun confirmed Game Crew and Tags remain human-testable through the API/database path.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# PR_26177_ALFA_059 Game Crew Foundation Report
2+
3+
## Summary
4+
- Preserved Game Crew foundation behavior: owner display, member list, add member, remove member, refresh persistence, and guest write redirect/401.
5+
- Reworked the shared Alfa tool runtime in this branch so Tags, Game Design, and Game Configuration no longer route through retired mock repositories.
6+
- Restored flat DB-backed Tags contract (`project_tags`, `project_tag_assignments`) and removed old `workspace_tag_records` metadata from active code paths.
7+
- Added/kept guardrail coverage that fails when retired Alfa mock repository files exist or are imported.
8+
- Removed the accidental `mock-db-store.js` and `AdminDbViewer.spec.mjs` expansion from this PR; active Game Crew and Tags behavior is validated through API/database services.
9+
10+
## Validation
11+
- PASS - `node --check src/dev-runtime/toolbox-api/alfa-tool-services.mjs`
12+
- PASS - `node --check src/dev-runtime/server/local-api-router.mjs`
13+
- PASS - `node --check assets/toolbox/game-crew/js/index.js`
14+
- PASS - `node --check assets/toolbox/tags/js/index.js`
15+
- PASS - `node --test tests/dev-runtime/DevRuntimeBoundary.test.mjs`
16+
- PASS - `npx playwright test tests/playwright/tools/GameCrewFoundation.spec.mjs --project=playwright --workers=1 --reporter=line` (5 passed)
17+
- PASS - `npx playwright test tests/playwright/tools/TagsTool.spec.mjs --project=playwright --workers=1 --reporter=line` (4 passed)
18+
19+
## Removed Mock Repository Files
20+
- `src/dev-runtime/persistence/tool-repositories/tags-mock-repository.js`
21+
- `src/dev-runtime/persistence/tool-repositories/game-design-mock-repository.js`
22+
- `src/dev-runtime/persistence/tool-repositories/game-configuration-mock-repository.js`
23+
24+
## Status
25+
PASS - PR059 is reworked, validated, and ready for owner testing after package generation.
26+
27+
## EOD Revalidation
28+
2026-06-26 - Reconfirmed the PR059 branch has no `mock-db-store.js` or `AdminDbViewer.spec.mjs` delta, reran Game Crew and Tags focused Playwright lanes, reran DevRuntimeBoundary, and rebuilt the delta ZIP.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# PR_26177_ALFA_059 Requirement Checklist
2+
3+
| Requirement | Status | Notes |
4+
| --- | --- | --- |
5+
| Game Crew shows current Game Hub game context | PASS | Playwright page coverage passed. |
6+
| Game Crew shows owner | PASS | Playwright page coverage passed. |
7+
| Game Crew shows member list | PASS | Playwright page coverage passed. |
8+
| Add member persists through API/DB | PASS | API and page coverage passed. |
9+
| Remove member persists through API/DB | PASS | API and page coverage passed. |
10+
| Guest add/remove redirects or 401s | PASS | Guest UI/API coverage passed. |
11+
| No mock repository source of truth for Tags/Design/Configuration | PASS | Files removed; guardrail passed. |
12+
| No mock-db source of truth for Tags/Game Crew | PASS | Accidental `mock-db-store.js` and Admin DB viewer expansion was removed; active behavior uses API/database services. |
13+
| Browser does not own product data | PASS | Writes route through API/database services. |
14+
| No JSON source of truth, SQLite, or tmp runtime dependency | PASS | None added. |
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# PR_26177_ALFA_059 Validation Lane
2+
3+
- PASS - `node --check src/dev-runtime/toolbox-api/alfa-tool-services.mjs`
4+
- PASS - `node --check src/dev-runtime/server/local-api-router.mjs`
5+
- PASS - `node --check assets/toolbox/game-crew/js/index.js`
6+
- PASS - `node --check assets/toolbox/tags/js/index.js`
7+
- PASS - `node --test tests/dev-runtime/DevRuntimeBoundary.test.mjs`
8+
- PASS - `npx playwright test tests/playwright/tools/GameCrewFoundation.spec.mjs --project=playwright --workers=1 --reporter=line`
9+
- PASS - `npx playwright test tests/playwright/tools/TagsTool.spec.mjs --project=playwright --workers=1 --reporter=line`
10+
11+
Notes: A parallel Tags run hit a Playwright artifact cleanup race after Game Crew passed; rerunning Tags by itself passed.

0 commit comments

Comments
 (0)