Skip to content

Commit a8164d8

Browse files
author
DavidQ
committed
Separate Workspace Manager V2 tools control and add Workflow nav entry - PR_26127_002-workspace-manager-v2-tools-control-and-nav
1 parent e3ee0a6 commit a8164d8

6 files changed

Lines changed: 122 additions & 18 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# PR_26127_002-workspace-manager-v2-tools-control-and-nav
2+
3+
## Scope
4+
5+
- Read `docs/dev/PROJECT_INSTRUCTIONS.md` before implementation.
6+
- Moved Workspace Manager V2 launch tiles into their own accordion control above Workspace Context.
7+
- Labeled the new Workspace Manager V2 control `Tools`.
8+
- Kept Workspace Manager V2 tool tile launch behavior unchanged.
9+
- Made Workspace Context summary tiles a consistent fixed height.
10+
- Added a `Workflow` group to `tools/index.html` above `Editors`.
11+
- Moved Workspace Manager V2 into the `Workflow` group while preserving `Editors`, `Utilities`, and `Viewers`.
12+
- Kept tile action labels as `How To Use`, `Read Me`, and `Samples (x)`.
13+
14+
## Validation
15+
16+
- `npm run test:workspace-v2` - PASS, 24 Playwright tests.
17+
- `git diff --check` - PASS with only Git line-ending warnings.
18+
- Scope check: no changes under `tools/workspace-v2` or `samples`.
19+
- Full samples smoke test skipped by request; this PR is Workspace Manager V2 UI/nav scoped.
20+
21+
## Playwright Impacted
22+
23+
Yes. This PR changes Workspace Manager V2 UI layout and the tools index navigation grouping.
24+
25+
Validated behavior:
26+
- Tools index shows `Workflow`, `Editors`, `Utilities`, and `Viewers` in that order.
27+
- Workspace Manager V2 appears in the `Workflow` group and not in `Utilities`.
28+
- Tool card action labels use `How To Use`, `Read Me`, and `Samples (x)` formatting.
29+
- Workspace Manager V2 has a separate `Tools` accordion above `Workspace Context`.
30+
- Tool tiles are no longer nested in Workspace Context.
31+
- Workspace Context summary tiles render at a consistent height.
32+
- Workspace Manager V2 tool tile launches and Return to Workspace behavior remain valid.
33+
34+
Expected pass behavior:
35+
- Workspace Manager V2 launches from tools/index.html under `Workflow`.
36+
- Workspace Manager V2 shows the `Tools` control above `Workspace Context`.
37+
- Workspace Context summary tiles align at the same height.
38+
39+
Expected fail behavior:
40+
- Invalid manifests still block export and log schema errors.
41+
- Direct Asset Manager V2 workspace query launch remains blocked.
42+
43+
## Manual Validation Notes
44+
45+
1. Open `/tools/index.html`.
46+
2. Confirm `Workflow` appears above `Editors`, followed by `Utilities` and `Viewers`.
47+
3. Confirm Workspace Manager V2 appears under `Workflow`.
48+
4. Confirm tool card actions read `How To Use`, `Read Me`, and `Samples (x)` where sample links exist.
49+
5. Open `/tools/workspace-manager-v2/index.html`.
50+
6. Confirm the center panel shows `Tools` above `Workspace Context`.
51+
7. Load Asteroids and confirm Workspace Context summary tiles have matching heights.
52+
8. Launch a tile, return to Workspace, and confirm the active context remains restored.
53+
54+
Out of scope:
55+
- Full samples smoke test.
56+
- Deprecated `tools/workspace-v2`.
57+
- Sample JSON changes.
58+

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,34 @@ test.describe("Workspace Manager V2 bootstrap", () => {
4343
await expect(card.locator("a", { hasText: "Workspace Manager V2" })).toHaveAttribute("href", "/tools/workspace-manager-v2/index.html");
4444
await expect(card).toContainText("First-Class Tool V2 workspace surface");
4545
await expect(card).toContainText("Workspace");
46+
const toolsIndexState = await page.evaluate(() => {
47+
const firstClassToolsSection = Array.from(document.querySelectorAll("section"))
48+
.find((section) => section.querySelector(":scope > h2")?.textContent?.trim() === "First-Class Tools");
49+
const workflowGrid = firstClassToolsSection?.querySelector("[data-active-tools-workflow-grid]");
50+
const utilitiesGrid = firstClassToolsSection?.querySelector("[data-active-tools-utilities-grid]");
51+
const workspaceCard = workflowGrid?.querySelector(".tools-platform-card");
52+
return {
53+
actionLabels: Array.from(firstClassToolsSection?.querySelectorAll(".tools-platform-card__action") || [])
54+
.map((action) => action.textContent.trim()),
55+
headings: Array.from(firstClassToolsSection?.querySelectorAll(":scope > h3") || [])
56+
.map((heading) => heading.textContent.trim()),
57+
sampleLabels: Array.from(firstClassToolsSection?.querySelectorAll(".tools-platform-card__action") || [])
58+
.map((action) => action.textContent.trim())
59+
.filter((label) => label.startsWith("Samples")),
60+
utilitiesCards: Array.from(utilitiesGrid?.querySelectorAll(".tools-platform-card h3") || [])
61+
.map((heading) => heading.textContent.trim()),
62+
workflowCards: Array.from(workflowGrid?.querySelectorAll(".tools-platform-card h3") || [])
63+
.map((heading) => heading.textContent.trim()),
64+
workspaceActionLabels: Array.from(workspaceCard?.querySelectorAll(".tools-platform-card__action") || [])
65+
.map((action) => action.textContent.trim())
66+
};
67+
});
68+
expect(toolsIndexState.headings.slice(0, 4)).toEqual(["Workflow", "Editors", "Utilities", "Viewers"]);
69+
expect(toolsIndexState.workflowCards).toEqual(["Workspace Manager V2"]);
70+
expect(toolsIndexState.utilitiesCards).not.toContain("Workspace Manager V2");
71+
expect(toolsIndexState.workspaceActionLabels).toEqual(["How To Use", "Read Me"]);
72+
expect(toolsIndexState.actionLabels).not.toContain("README");
73+
expect(toolsIndexState.sampleLabels.every((label) => /^Samples \(\d+\)$/.test(label))).toBe(true);
4674
expect(pageErrors).toEqual([]);
4775
} finally {
4876
await coverageReporter.stop(page);
@@ -140,6 +168,11 @@ test.describe("Workspace Manager V2 bootstrap", () => {
140168
await expect(page.locator("#seedUatManifestButton")).toBeHidden();
141169
await expect(page.locator("#loadAsteroidsButton")).toHaveText("Load Asteroids");
142170
await expect(page.locator("#launchAssetManagerV2Button")).toHaveCount(0);
171+
await expect(page.locator("#workspaceToolsContent #workspaceToolTiles")).toBeVisible();
172+
await expect(page.locator("#workspaceContextContent #workspaceToolTiles")).toHaveCount(0);
173+
const centerControlLabels = await page.locator(".workspace-manager-v2__panel--center > .accordion-v2 > .accordion-v2__header span:first-child")
174+
.evaluateAll((labels) => labels.map((label) => label.textContent.trim()));
175+
expect(centerControlLabels.slice(0, 2)).toEqual(["Tools", "Workspace Context"]);
143176
await expect(page.locator("#workspaceToolTiles [data-workspace-tool-id]")).toHaveCount(5);
144177
expect(await page.locator("#workspaceToolTiles [data-workspace-tool-id]").evaluateAll((tiles) => tiles.every((tile) => tile.disabled))).toBe(true);
145178
await expect(page.locator("#activeGameSelect option")).toHaveText([
@@ -155,6 +188,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
155188
await expect(page.locator("#activePaletteSummary")).toContainText("active colors");
156189
await expect(page.locator("#activeAssetRegistrySummary")).toHaveText("Schema-ready Asset Manager V2 manifest payload contains 13 managed assets.");
157190
await expect(page.locator("#launchContextSummary")).toHaveText("Schema-valid manifest is ready for Asteroids.");
191+
const workspaceContextTileHeights = await page.locator(".workspace-manager-v2__summary-card").evaluateAll((tiles) => tiles.map((tile) => Math.round(tile.getBoundingClientRect().height)));
192+
expect(new Set(workspaceContextTileHeights).size).toBe(1);
193+
expect(workspaceContextTileHeights.every((height) => height >= 118)).toBe(true);
158194
await expect(page.locator("#workspaceContextOutput")).toContainText('"gameRoot": "games/Asteroids/"');
159195
await expect(page.locator("#workspaceContextOutput")).toContainText('"assetsPath": "games/Asteroids/assets"');
160196
await expect(page.locator("#workspaceContextOutput")).toContainText('"source": "manifest"');

tools/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
<section>
3131
<h2>First-Class Tools</h2>
32+
<h3>Workflow</h3>
33+
<div class="grid" data-active-tools-workflow-grid></div>
3234
<h3>Editors</h3>
3335
<div class="grid" data-active-tools-editors-grid></div>
3436
<h3>Utilities</h3>

tools/renderToolsIndex.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function buildDocumentationLinks(tool) {
1616
}
1717
return [
1818
{ label: "How To Use", path: `${folder}/how_to_use.html` },
19-
{ label: "README", path: `${folder}/README.md` }
19+
{ label: "Read Me", path: `${folder}/README.md` }
2020
];
2121
}
2222

@@ -73,6 +73,9 @@ function renderToolCard(tool, sampleCountByToolId) {
7373
}
7474

7575
function classifyToolGroup(toolId) {
76+
const workflowToolIds = new Set([
77+
"workspace-manager-v2"
78+
]);
7679
const viewerToolIds = new Set([
7780
"3d-asset-viewer",
7881
"replay-visualizer",
@@ -82,11 +85,13 @@ function classifyToolGroup(toolId) {
8285
"asset-browser",
8386
"asset-manager-v2",
8487
"asset-pipeline",
85-
"workspace-manager-v2",
8688
"tile-model-converter",
8789
"physics-sandbox",
8890
"3d-json-payload"
8991
]);
92+
if (workflowToolIds.has(toolId)) {
93+
return "workflow";
94+
}
9095
if (viewerToolIds.has(toolId)) {
9196
return "viewers";
9297
}
@@ -136,10 +141,11 @@ async function loadSampleCountByToolId() {
136141
}
137142

138143
function renderActiveToolsList(sampleCountByToolId) {
144+
const workflowGrid = document.querySelector("[data-active-tools-workflow-grid]");
139145
const editorsGrid = document.querySelector("[data-active-tools-editors-grid]");
140146
const utilitiesGrid = document.querySelector("[data-active-tools-utilities-grid]");
141147
const viewersGrid = document.querySelector("[data-active-tools-viewers-grid]");
142-
if (!editorsGrid || !utilitiesGrid || !viewersGrid) {
148+
if (!workflowGrid || !editorsGrid || !utilitiesGrid || !viewersGrid) {
143149
return;
144150
}
145151
const tools = getToolRegistry()
@@ -148,10 +154,12 @@ function renderActiveToolsList(sampleCountByToolId) {
148154
.filter((entry) => entry.id !== "state-inspector")
149155
.sort((left, right) => String(left.displayName || "").localeCompare(String(right.displayName || "")));
150156

157+
const workflow = tools.filter((tool) => classifyToolGroup(tool.id) === "workflow").map((tool) => renderToolCard(tool, sampleCountByToolId));
151158
const editors = tools.filter((tool) => classifyToolGroup(tool.id) === "editors").map((tool) => renderToolCard(tool, sampleCountByToolId));
152159
const utilities = tools.filter((tool) => classifyToolGroup(tool.id) === "utilities").map((tool) => renderToolCard(tool, sampleCountByToolId));
153160
const viewers = tools.filter((tool) => classifyToolGroup(tool.id) === "viewers").map((tool) => renderToolCard(tool, sampleCountByToolId));
154161

162+
workflowGrid.innerHTML = workflow.join("\n");
155163
editorsGrid.innerHTML = editors.join("\n");
156164
utilitiesGrid.innerHTML = utilities.join("\n");
157165
viewersGrid.innerHTML = viewers.join("\n");

tools/workspace-manager-v2/index.html

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ <h2 class="tools-platform-frame__eyebrow">Games-only launch context</h2>
5959
</aside>
6060

6161
<section class="workspace-manager-v2__panel workspace-manager-v2__panel--center" aria-label="Workspace context">
62+
<section class="accordion-v2 workspace-manager-v2__accordion workspace-manager-v2__accordion--tools is-open" data-accordion-v2-open="true">
63+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="workspaceToolsContent">
64+
<span>Tools</span>
65+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
66+
</button>
67+
<div id="workspaceToolsContent" class="accordion-v2__content">
68+
<div id="workspaceToolTiles" class="workspace-manager-v2__tool-grid" aria-label="Workspace launchable tools"></div>
69+
</div>
70+
</section>
71+
6272
<section class="accordion-v2 workspace-manager-v2__accordion workspace-manager-v2__accordion--fill is-open" data-accordion-v2-open="true">
6373
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="workspaceContextContent">
6474
<span>Workspace Context</span>
@@ -79,10 +89,6 @@ <h2>Launch Context</h2>
7989
<p id="launchContextSummary">Asset Manager V2 launch is waiting for a game and palette.</p>
8090
</section>
8191
</div>
82-
<section class="workspace-manager-v2__tool-launcher" aria-label="Workspace launchable tools">
83-
<h2>Tools</h2>
84-
<div id="workspaceToolTiles" class="workspace-manager-v2__tool-grid"></div>
85-
</section>
8692
<pre id="workspaceContextOutput" class="workspace-manager-v2__output">{}</pre>
8793
</div>
8894
</section>

tools/workspace-manager-v2/styles/workspaceManagerV2.css

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -271,14 +271,19 @@ textarea:hover {
271271
.workspace-manager-v2__summary-grid {
272272
display: grid;
273273
grid-template-columns: repeat(3, minmax(0, 1fr));
274+
grid-auto-rows: minmax(118px, auto);
274275
gap: 12px;
275276
}
276277

277278
.workspace-manager-v2__summary-card {
278279
min-width: 0;
280+
height: 118px;
281+
display: flex;
282+
flex-direction: column;
279283
border: 1px solid var(--workspace-manager-v2-line);
280284
border-radius: 8px;
281285
background: var(--workspace-manager-v2-surface);
286+
overflow: auto;
282287
padding: 12px;
283288
}
284289

@@ -297,17 +302,6 @@ textarea:hover {
297302
line-height: 1.45;
298303
}
299304

300-
.workspace-manager-v2__tool-launcher {
301-
display: grid;
302-
gap: 10px;
303-
}
304-
305-
.workspace-manager-v2__tool-launcher h2 {
306-
margin: 0;
307-
color: var(--workspace-manager-v2-muted);
308-
font-size: 0.95rem;
309-
}
310-
311305
.workspace-manager-v2__tool-grid {
312306
display: grid;
313307
grid-template-columns: repeat(auto-fill, minmax(180px, 180px));

0 commit comments

Comments
 (0)