Skip to content

Commit 98df80e

Browse files
committed
Clean up old toolbox beta path and add status kickers - PR_26159_053-tool-status-kickers-beta-access
1 parent 9634d1f commit 98df80e

8 files changed

Lines changed: 577 additions & 36 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[ ] context disable
2+
[ ] windows key disable
3+
[ ] disable keyboard special (function key, not fuction key actions
4+
[ ]public or private
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# PR_26159_053 Tool Status Kickers Beta Access
2+
3+
## Executive Summary
4+
5+
Status kickers are now registry-driven on the Toolbox index. Existing internal `status` metadata is preserved for compatibility, while user-facing tiles derive `releaseChannel`, `releaseChannelLabel`, and exact hover/help text. AI Assistant is the first beta-restricted tool through `requiredRole: "beta"`, with User 2 seeded as the local beta tester.
6+
7+
Playwright impacted: Yes.
8+
9+
## Branch Guard
10+
11+
| Check | Status | Evidence |
12+
| --- | --- | --- |
13+
| Current branch is main | PASS | `git branch --show-current` returned `main`. |
14+
| Local branch list checked | PASS | `git branch --list` returned `* main`. |
15+
| Worktree inspected before edits | PASS | Initial `git status --short --untracked-files=all` was clean. |
16+
17+
## Old Status/Beta Cleanup
18+
19+
| Area | Result |
20+
| --- | --- |
21+
| Old code found | Existing active Toolbox code used `TOOL_STATUS_MODEL` and visible tile labels `Ready`, `Wireframe`, `Planned`, `Hidden`; `tools-page-accordions.js` filtered creator tiles with `tool.status === "Ready"`. |
22+
| Files cleaned | `toolbox/tools-page-accordions.js` now derives display/status filtering from release channels; the old visible `Ready` pill text is replaced by `Complete`. |
23+
| No obsolete beta route found | No active `{tool}-beta` routes, beta-only pages, beta-only launch code, or voting code existed before this PR. |
24+
| Intentionally preserved | `TOOL_STATUS_MODEL`, `status`, readiness metadata, Build Path status logic, `adminOnly`, `hidden`, and existing stable tool routes are preserved for compatibility. |
25+
| Why preserved | Other admin/progress/build-path tooling still consumes legacy registry status fields; release channels are added as the clean user-facing access layer. |
26+
27+
## Requirement Checklist
28+
29+
| Requirement | Status | Evidence |
30+
| --- | --- | --- |
31+
| Read project instructions first | PASS | `docs_build/dev/PROJECT_INSTRUCTIONS.md` read before edits. |
32+
| Hard stop unless on main | PASS | Branch guard passed on `main`. |
33+
| Remove/report obsolete status/beta/vote/filter code | PASS | Cleanup section above; no stale beta/vote implementation found. |
34+
| Toolbox tiles show Planned/Wireframe/Beta/Complete kickers | PASS | `toolbox/toolRegistry.js` derives release channel labels; `toolbox/tools-page-accordions.js` renders `data-toolbox-kicker`. |
35+
| Kicker hover/help text matches requested wording | PASS | Help text constants in `toolbox/toolRegistry.js` and Playwright title assertions in `ToolboxRoutePages.spec.mjs`. |
36+
| Status count/filter controls side by side | PASS | `toolbox/index.html` adds `data-toolbox-status-filters`; Playwright verifies same-row button positions. |
37+
| Default filters Complete, Beta, Wireframe | PASS | `visibleReleaseChannels` defaults to `complete`, `beta`, `wireframe`; Playwright checks `aria-pressed`. |
38+
| Planned visible only when selected | PASS | Planned filter defaults false; Admin Build Game appears only after toggling Planned. |
39+
| No `{tool}-beta` files/routes | PASS | Static `rg --files` check found no `-beta` route/file; stable hrefs asserted. |
40+
| Registry-driven beta access | PASS | AI Assistant has `releaseChannel: "beta"` and `requiredRole: "beta"` in `toolbox/toolRegistry.js`. |
41+
| Non-beta beta launch blocked | PASS | User 1 click shows `This tool is in beta. Request beta access to try it.` |
42+
| Beta role launches beta tool | PASS | User 2 has seeded `beta` role and launches `/toolbox/ai-assistant/index.html`. |
43+
| Admin launches beta tool | PASS | DavidQ/admin launches `/toolbox/ai-assistant/index.html`. |
44+
| Complete tools launch normally | PASS | Existing stable Complete tool URLs remain unchanged; Colors remains `/toolbox/colors/index.html`. |
45+
| Wireframe tools open preview pages | PASS | Wireframe action links are not blocked and are labeled `Open Preview`. |
46+
| Planned tools show details/vote/feedback instead of runtime launch | PASS | Planned click stays on Toolbox and updates planned status; planned card includes non-persistent feedback controls. |
47+
| Planned/Wireframe voting controls render only there | PASS | Playwright verifies Build Game and Fonts vote controls; renderer returns no controls for Beta/Complete. |
48+
| No hidden vote persistence invented | PASS | Vote handlers update only the page status message. |
49+
| No inline script/style/event handlers | PASS | Toolbox source checked in Playwright; no inline script/style/onclick added. |
50+
| No console errors | PASS | Toolbox Playwright lane captured no repo-owned console errors. |
51+
52+
## Validation Evidence
53+
54+
| Lane | Status | Evidence |
55+
| --- | --- | --- |
56+
| Changed-file syntax | PASS | `node --check toolbox/tools-page-accordions.js`, `toolbox/toolRegistry.js`, `src/dev-runtime/persistence/mock-db-store.js`, and `tests/playwright/tools/ToolboxRoutePages.spec.mjs`. |
57+
| Toolbox page/runtime Playwright | PASS | `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --reporter=line` passed 3/3. |
58+
| Registry validation | PASS | `node scripts/validate-tool-registry.mjs` returned `TOOL_REGISTRY_VALID`. |
59+
| Diff whitespace | PASS | `git diff --check` passed; only existing LF/CRLF warnings were reported. |
60+
| `{tool}-beta` static audit | PASS | `rg --files | rg "(^|/|\\\\)[^/\\\\]+-beta(\\.|/|\\\\)|beta-only|tool-vote|vote-tool"` returned no matches. |
61+
62+
## Skipped Lanes
63+
64+
| Lane | Why Safe to Skip |
65+
| --- | --- |
66+
| Full samples validation | PR touches Toolbox registry/index behavior and local seed roles only; no sample loader/runtime framework changed. |
67+
| Full Playwright suite | Targeted Toolbox route/status/access lane covered the affected surface; full suite is outside this PR scope. |
68+
69+
## Notes
70+
71+
- Planned tools are intentionally not launched from Toolbox tiles, including for admin, because this PR explicitly says planned tools should show planned details/vote/feedback instead of launching unfinished runtime.
72+
- A pre-existing/generated `docs_build/dev/reports/tool_registry_validation.txt` was updated by the registry validation lane.
73+
- An unrelated untracked file, `docs_build/dev/admin-notes/engine/game setup.txt`, is present in the worktree and was not modified or packaged.
Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,89 @@
11
TOOL_REGISTRY_VALIDATION
22
status=PASS
3-
active=AI Assistant, Animation, Assets, Cloud, Custom Extensions, Game Design, Input, Localization, MIDI, Object Vector, Palette Manager, Particles, Publish, Sound, Storage Inspector, World Vector, Game Configuration, Project Workspace
3+
active=AI Assistant, Project Workspace, Project Journey, Game Design, Game Configuration, Assets, Colors, Fonts, Sprites, Characters, Objects, Worlds, Animations, Audio, Music, Voices, Videos, Build Game, Game Testing, Controls, Hitboxes, Saved Data, Debug, Performance, Events, Publish, Marketplace, Community, Languages, Achievements, Ratings, Cloud, Custom Extensions, MIDI, Particles, Audio Effects, Voice Capture, Voice Output
44
Filesystem Active Tool Directories
5+
- achievements
56
- ai-assistant
6-
- animation
7+
- animations
78
- assets
9+
- audio
10+
- audio-effects
11+
- build-game
12+
- characters
813
- cloud
914
- code
15+
- colors
16+
- community
17+
- controls
18+
- debug
19+
- events
20+
- fonts
1021
- game-configuration
1122
- game-design
12-
- input
13-
- localization
23+
- game-testing
24+
- hitboxes
25+
- languages
26+
- marketplace
1427
- midi
15-
- object-vector
16-
- palette
28+
- music
29+
- objects
1730
- particles
31+
- performance
32+
- project-journey
1833
- project-workspace
1934
- publish
20-
- sound
21-
- storage
22-
- world-vector
35+
- ratings
36+
- saved-data
37+
- speech-to-text
38+
- sprites
39+
- text-to-speech
40+
- videos
41+
- voices
42+
- worlds
2343
Registry Folders
2444
- ai-assistant
25-
- animation
45+
- project-workspace
46+
- project-journey
47+
- game-design
48+
- game-configuration
2649
- assets
50+
- colors
51+
- fonts
52+
- sprites
53+
- characters
54+
- objects
55+
- worlds
56+
- animations
57+
- audio
58+
- music
59+
- voices
60+
- videos
61+
- build-game
62+
- game-testing
63+
- controls
64+
- hitboxes
65+
- saved-data
66+
- debug
67+
- performance
68+
- events
69+
- publish
70+
- marketplace
71+
- community
72+
- languages
73+
- achievements
74+
- ratings
2775
- cloud
2876
- code
29-
- game-design
30-
- game-configuration
31-
- input
32-
- localization
3377
- midi
34-
- object-vector
35-
- palette
3678
- particles
37-
- publish
38-
- project-workspace
39-
- sound
40-
- storage
41-
- world-vector
79+
- audio-effects
80+
- speech-to-text
81+
- text-to-speech
82+
- users
83+
- environments
84+
- game-migration
85+
- platform-settings
4286
Issues
4387
- none
4488
Notes
45-
- registry, toolbox folders, and toolbox index routes are aligned
89+
- registry, toolbox folders, and registry-driven toolbox index routes are aligned

src/dev-runtime/persistence/mock-db-store.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const MOCK_DB_KEYS = Object.freeze({
2121
user: makeMockUlid(72),
2222
admin: makeMockUlid(73),
2323
system: makeMockUlid(74),
24+
beta: makeMockUlid(75),
2425
}),
2526
userRoles: Object.freeze({
2627
user1User: makeMockUlid(82),
@@ -29,6 +30,7 @@ export const MOCK_DB_KEYS = Object.freeze({
2930
adminUser: makeMockUlid(85),
3031
adminAdmin: makeMockUlid(86),
3132
forgeBotSystem: makeMockUlid(87),
33+
user2Beta: makeMockUlid(88),
3234
}),
3335
});
3436

@@ -730,14 +732,24 @@ export function getStandaloneMockDbSeedTables() {
730732
isActive: true,
731733
...standaloneAudit(8),
732734
},
735+
{
736+
key: MOCK_DB_KEYS.roles.beta,
737+
roleSlug: "beta",
738+
name: "beta",
739+
description: "Local beta access tester.",
740+
isSystemRole: false,
741+
isActive: true,
742+
...standaloneAudit(9),
743+
},
733744
],
734745
user_roles: [
735-
{ key: MOCK_DB_KEYS.userRoles.user1User, userKey: MOCK_DB_KEYS.users.user1, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(9) },
736-
{ key: MOCK_DB_KEYS.userRoles.user2User, userKey: MOCK_DB_KEYS.users.user2, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(10) },
737-
{ key: MOCK_DB_KEYS.userRoles.user3User, userKey: MOCK_DB_KEYS.users.user3, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(11) },
738-
{ key: MOCK_DB_KEYS.userRoles.adminUser, userKey: MOCK_DB_KEYS.users.admin, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(12) },
739-
{ key: MOCK_DB_KEYS.userRoles.adminAdmin, userKey: MOCK_DB_KEYS.users.admin, roleKey: MOCK_DB_KEYS.roles.admin, ...standaloneAudit(13) },
740-
{ key: MOCK_DB_KEYS.userRoles.forgeBotSystem, userKey: MOCK_DB_KEYS.users.forgeBot, roleKey: MOCK_DB_KEYS.roles.system, ...standaloneAudit(14) },
746+
{ key: MOCK_DB_KEYS.userRoles.user1User, userKey: MOCK_DB_KEYS.users.user1, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(10) },
747+
{ key: MOCK_DB_KEYS.userRoles.user2User, userKey: MOCK_DB_KEYS.users.user2, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(11) },
748+
{ key: MOCK_DB_KEYS.userRoles.user2Beta, userKey: MOCK_DB_KEYS.users.user2, roleKey: MOCK_DB_KEYS.roles.beta, ...standaloneAudit(12) },
749+
{ key: MOCK_DB_KEYS.userRoles.user3User, userKey: MOCK_DB_KEYS.users.user3, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(13) },
750+
{ key: MOCK_DB_KEYS.userRoles.adminUser, userKey: MOCK_DB_KEYS.users.admin, roleKey: MOCK_DB_KEYS.roles.user, ...standaloneAudit(14) },
751+
{ key: MOCK_DB_KEYS.userRoles.adminAdmin, userKey: MOCK_DB_KEYS.users.admin, roleKey: MOCK_DB_KEYS.roles.admin, ...standaloneAudit(15) },
752+
{ key: MOCK_DB_KEYS.userRoles.forgeBotSystem, userKey: MOCK_DB_KEYS.users.forgeBot, roleKey: MOCK_DB_KEYS.roles.system, ...standaloneAudit(16) },
741753
],
742754
};
743755
}

tests/playwright/tools/ToolboxRoutePages.spec.mjs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ test("toolbox index shows all registered list tools for Admin and preserves crea
9292
try {
9393
await setServerSession(server, MOCK_DB_KEYS.users.user1);
9494
await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
95+
await expect(page.locator("[data-toolbox-tool-name-link='AI Assistant']")).toBeVisible();
9596
await expect(page.locator("[data-toolbox-tool-name-link='Colors']")).toBeVisible();
9697
await expect(page.locator("[data-toolbox-tool-name-link='Fonts']")).toHaveCount(0);
9798
await expect(page.locator("[data-toolbox-tool-name-link='Assets']")).toBeVisible();
@@ -102,7 +103,119 @@ test("toolbox index shows all registered list tools for Admin and preserves crea
102103
await expect(page.locator("[data-toolbox-tool-name-link='Colors']")).toHaveAttribute("href", "/toolbox/colors/index.html");
103104
await expect(page.locator("[data-toolbox-tool-name-link='Fonts']")).toBeVisible();
104105
const colorsCard = page.locator("[data-toolbox-tool-name-link='Colors']").locator("xpath=ancestor::article[1]");
105-
await expect(colorsCard.locator("[data-toolbox-readiness]")).toHaveText("Ready");
106+
await expect(colorsCard.locator("[data-toolbox-readiness]")).toHaveText("Complete");
107+
108+
expect(failedRequests).toEqual([]);
109+
expect(pageErrors).toEqual([]);
110+
expect(consoleErrors).toEqual([]);
111+
} finally {
112+
await server.close();
113+
}
114+
});
115+
116+
test("toolbox status kickers, filters, beta access, and voting controls work from registry metadata", async ({ page }) => {
117+
const server = await startRepoServer();
118+
const failedRequests = [];
119+
const pageErrors = [];
120+
const consoleErrors = [];
121+
122+
page.on("response", (response) => {
123+
if (response.status() >= 400) {
124+
failedRequests.push(`${response.status()} ${response.url()}`);
125+
}
126+
});
127+
page.on("requestfailed", (request) => {
128+
failedRequests.push(`FAILED ${request.url()}`);
129+
});
130+
page.on("pageerror", (error) => {
131+
const text = error.stack || error.message;
132+
if (!isBrowserExtensionNoise(text)) {
133+
pageErrors.push(error.message);
134+
}
135+
});
136+
page.on("console", (message) => {
137+
if (message.type() === "error" && !isBrowserExtensionNoise(message.text())) {
138+
consoleErrors.push(message.text());
139+
}
140+
});
141+
142+
try {
143+
await setServerSession(server, MOCK_DB_KEYS.users.user1);
144+
await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
145+
146+
await expect(page.locator("[data-toolbox-status-filter]")).toHaveText([
147+
/Complete \(\d+\)/,
148+
/Beta \(\d+\)/,
149+
/Wireframe \(\d+\)/,
150+
/Planned \(\d+\)/,
151+
]);
152+
await expect(page.locator("[data-toolbox-status-filter='complete']")).toHaveAttribute("aria-pressed", "true");
153+
await expect(page.locator("[data-toolbox-status-filter='beta']")).toHaveAttribute("aria-pressed", "true");
154+
await expect(page.locator("[data-toolbox-status-filter='wireframe']")).toHaveAttribute("aria-pressed", "true");
155+
await expect(page.locator("[data-toolbox-status-filter='planned']")).toHaveAttribute("aria-pressed", "false");
156+
const statusFilterTopPositions = await page.locator("[data-toolbox-status-filter]").evaluateAll((buttons) => (
157+
buttons.map((button) => Math.round(button.getBoundingClientRect().top))
158+
));
159+
expect(new Set(statusFilterTopPositions).size).toBe(1);
160+
161+
const betaCard = page.locator("[data-toolbox-tool-card='AI Assistant']");
162+
await expect(betaCard).toBeVisible();
163+
await expect(betaCard.locator("[data-toolbox-kicker]")).toHaveText("Beta");
164+
await expect(betaCard.locator("[data-toolbox-kicker]")).toHaveAttribute(
165+
"title",
166+
"Ready to try.\nFeatures, layout, and workflows may change based on feedback.",
167+
);
168+
await expect(betaCard.locator("[data-toolbox-launch-link='AI Assistant']").first()).not.toHaveAttribute("href", /-beta/);
169+
await betaCard.locator("[data-toolbox-tile-action-row='AI Assistant'] a.btn").click();
170+
await expect(page.locator("[data-toolbox-launch-status]")).toHaveText("This tool is in beta. Request beta access to try it.");
171+
172+
await expect(page.locator("[data-toolbox-tool-card='Build Game']")).toHaveCount(0);
173+
await page.locator("[data-toolbox-status-filter='planned']").click();
174+
await expect(page.locator("[data-toolbox-tool-card='Build Game']")).toHaveCount(0);
175+
176+
await setServerSession(server, MOCK_DB_KEYS.users.user2);
177+
await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
178+
await page.locator("[data-toolbox-tool-card='AI Assistant'] [data-toolbox-tile-action-row='AI Assistant'] a.btn").click();
179+
await page.waitForURL(/\/toolbox\/ai-assistant\/index\.html$/);
180+
181+
await setServerSession(server, MOCK_DB_KEYS.users.admin);
182+
await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
183+
await expect(page.locator("[data-toolbox-tool-card='Build Game']")).toHaveCount(0);
184+
await page.locator("[data-toolbox-status-filter='planned']").click();
185+
const plannedCard = page.locator("[data-toolbox-tool-card='Build Game']");
186+
await expect(plannedCard).toBeVisible();
187+
await expect(plannedCard.locator("[data-toolbox-kicker]")).toHaveText("Planned");
188+
await expect(plannedCard.locator("[data-toolbox-kicker]")).toHaveAttribute("title", "Idea exists.\nNot yet available.");
189+
await expect(plannedCard.locator("[data-toolbox-vote-controls='Build Game']")).toBeVisible();
190+
await plannedCard.getByRole("button", { name: "Up vote Build Game" }).click();
191+
await expect(page.locator("[data-toolbox-launch-status]")).toHaveText("Build Game up vote noted as a non-persistent wireframe control.");
192+
await plannedCard.locator("[data-toolbox-tile-action-row='Build Game'] a.btn").click();
193+
await expect(page).toHaveURL(/\/toolbox\/index\.html$/);
194+
await expect(page.locator("[data-toolbox-launch-status]")).toContainText("Build Game is planned.");
195+
196+
const wireframeCard = page.locator("[data-toolbox-tool-card='Fonts']");
197+
await expect(wireframeCard.locator("[data-toolbox-kicker]")).toHaveText("Wireframe");
198+
await expect(wireframeCard.locator("[data-toolbox-kicker]")).toHaveAttribute(
199+
"title",
200+
"Preview the planned workflow and layout.\nHelp shape the design before development begins.",
201+
);
202+
await expect(wireframeCard.locator("[data-toolbox-vote-controls='Fonts']")).toBeVisible();
203+
await expect(page.locator("[data-toolbox-tool-card='Colors'] [data-toolbox-kicker]")).toHaveAttribute(
204+
"title",
205+
"Production ready and fully supported.",
206+
);
207+
208+
await page.goto(`${server.baseUrl}/toolbox/index.html`, { waitUntil: "networkidle" });
209+
await page.locator("[data-toolbox-tool-card='AI Assistant'] [data-toolbox-tile-action-row='AI Assistant'] a.btn").click();
210+
await page.waitForURL(/\/toolbox\/ai-assistant\/index\.html$/);
211+
212+
const toolboxSource = await page.evaluate(async () => {
213+
const response = await fetch("/toolbox/index.html");
214+
return response.text();
215+
});
216+
expect(toolboxSource).not.toMatch(/<script(?![^>]+src=)[^>]*>/i);
217+
expect(toolboxSource).not.toMatch(/<style[\s>]/i);
218+
expect(toolboxSource).not.toContain("onclick=");
106219

107220
expect(failedRequests).toEqual([]);
108221
expect(pageErrors).toEqual([]);

toolbox/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ <h1>Creator tools organized by build surface.</h1>
2727
<button class="btn" type="button" data-tools-view="build-path" aria-pressed="false">Build Path</button>
2828
<span class="pill" data-tools-count>Tool Count: 0/0</span>
2929
</div>
30+
<div class="hero-actions" aria-label="Tool status filters" data-toolbox-status-filters></div>
31+
<p class="status" role="status" data-toolbox-launch-status>Showing Complete, Beta, and Wireframe tools. Select Planned to include planned tools.</p>
3032
<div class="accordion-group" data-tools-accordion-list></div>
3133
</div>
3234
</section>

0 commit comments

Comments
 (0)