Skip to content

Commit ca93240

Browse files
author
DavidQ
committed
Add Workspace V2 tool launcher and rename Asset Browser V2 UI to Asset Manager V2 - PR_11_313
1 parent e02baf6 commit ca93240

11 files changed

Lines changed: 244 additions & 31 deletions

File tree

docs/dev/codex_commands.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,10 @@ PR_11_313
124124
```bash
125125
npx @openai/codex run --model gpt-5.3-codex --reasoning medium "Implement PR_11_313 ..."
126126
```
127+
128+
---
129+
PR_11_313 (Workspace V2 launcher + Asset Manager V2 UI labels)
130+
131+
```bash
132+
npx @openai/codex run --model gpt-5.3-codex --reasoning medium "Implement PR_11_313: Workspace V2 tool launcher UI + Asset Manager V2 user-facing rename."
133+
```

docs/dev/commit_comment.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Complete Asset Browser V2 strict payloadJson catalog handling with explicit valid-empty and invalid legacy hint rejection - PR 11.313
1+
Add explicit Workspace V2 Asset Manager launcher and rename Asset Browser V2 user-facing labels without contract changes - PR 11.313
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# PR_11_313 Workspace V2 Launcher + Asset Manager V2 UI Report
2+
3+
## Purpose
4+
Make Workspace V2 explicitly able to open tools via a visible tools menu and rename Asset Browser V2 to Asset Manager V2 in user-facing UI while keeping `asset-browser-v2` contracts intact.
5+
6+
## Files Changed
7+
- `tools/workspace-v2/index.html`
8+
- `tools/workspace-v2/index.js`
9+
- `tools/asset-browser-v2/index.html`
10+
- `tools/asset-browser-v2/index.js`
11+
- `tools/index.html`
12+
- `tests/runtime/V2WorkspaceAssetManagerLaunch.test.mjs`
13+
- `docs/pr/PR_11_313_WORKSPACE_V2_TOOL_LAUNCHER_ASSET_MANAGER_UI/PLAN_PR.md`
14+
- `docs/pr/PR_11_313_WORKSPACE_V2_TOOL_LAUNCHER_ASSET_MANAGER_UI/BUILD_PR.md`
15+
- `docs/dev/reports/PR_11_313_workspace_tool_launcher_asset_manager_report.md`
16+
- `docs/dev/codex_commands.md`
17+
- `docs/dev/commit_comment.txt`
18+
19+
## Implementation Summary
20+
- Added explicit Workspace V2 `Tools` section and direct `Open Asset Manager V2` button.
21+
- Direct launcher now builds an `asset-browser-v2` session using active Workspace `payloadJson` only and navigates with `hostContextId`.
22+
- User-facing rename to `Asset Manager V2` applied in:
23+
- Workspace V2 launcher labels
24+
- Asset Browser V2 page title and on-screen text
25+
- Tools index label
26+
- Contract preserved:
27+
- tool id remains `asset-browser-v2`
28+
- `data-tool-id="asset-browser-v2"` unchanged
29+
- existing invalid/empty behavior still used when `assetCatalog` is missing/invalid.
30+
31+
## Validation Commands Run
32+
- `node --check tools/workspace-v2/index.js` -> **PASS**
33+
- `node --check tools/asset-browser-v2/index.js` -> **PASS**
34+
- `node --check tests/runtime/V2WorkspaceAssetManagerLaunch.test.mjs` -> **PASS**
35+
- `node tests/runtime/V2WorkspaceAssetManagerLaunch.test.mjs` -> **PASS**
36+
37+
## Targeted Workspace V2 Launch Test
38+
- Confirms explicit tools menu section exists.
39+
- Confirms direct Asset Manager button exists and is wired.
40+
- Confirms launch path targets `asset-browser-v2` and passes active `payloadJson`.
41+
- Confirms user-facing rename is present while `asset-browser-v2` contract IDs remain unchanged.
42+
43+
## Full Samples Smoke
44+
- **Skipped intentionally**.
45+
- Reason: scope is isolated to Workspace V2 launcher wiring + user-facing labels, validated with targeted runtime test and syntax checks only.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# BUILD_PR_11_313
2+
3+
## Implementation
4+
- Added explicit Workspace V2 tools menu section with direct `Open Asset Manager V2` action.
5+
- Wired direct Asset Manager launch in Workspace V2:
6+
- Reads active Workspace session.
7+
- Requires active `payloadJson`.
8+
- Launches `tools/asset-browser-v2/index.html?hostContextId=<id>&fromTool=workspace-v2`.
9+
- Preserves `asset-browser-v2` tool/session contract and lets Asset Manager V2 show existing empty/invalid states when `assetCatalog` is not valid.
10+
- Renamed user-facing labels from Asset Browser V2 to Asset Manager V2 in:
11+
- Workspace V2 producer/tools labels
12+
- Asset Browser V2 page title/text
13+
- Tools index menu label
14+
- Kept contract identifiers unchanged (`asset-browser-v2`).
15+
16+
## Validation
17+
- `node --check tools/workspace-v2/index.js`
18+
- `node --check tools/asset-browser-v2/index.js`
19+
- `node --check tests/runtime/V2WorkspaceAssetManagerLaunch.test.mjs`
20+
- `node tests/runtime/V2WorkspaceAssetManagerLaunch.test.mjs`
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# PLAN_PR_11_313
2+
3+
## Purpose
4+
Enable explicit Workspace V2 tool launching and rename Asset Browser V2 to Asset Manager V2 in user-facing UI while preserving `asset-browser-v2` contract IDs and payload/session rules.
5+
6+
## Scope
7+
- `tools/workspace-v2/index.html`
8+
- `tools/workspace-v2/index.js`
9+
- `tools/asset-browser-v2/index.html`
10+
- `tools/asset-browser-v2/index.js`
11+
- `tools/index.html`
12+
- `tests/runtime/V2WorkspaceAssetManagerLaunch.test.mjs`
13+
- PR docs/reports for this change
14+
15+
## Steps
16+
1. Add an explicit Workspace V2 tools section/menu with a direct Asset Manager V2 launch control.
17+
2. Wire direct launch from Workspace V2 to `asset-browser-v2` using active `payloadJson` without changing tool/session contracts.
18+
3. Update user-facing labels from Asset Browser V2 to Asset Manager V2 (UI text/title only).
19+
4. Keep `toolId` and `data-tool-id` as `asset-browser-v2`.
20+
5. Add targeted runtime validation for launcher wiring and user-facing label/contract checks.
21+
6. Run syntax checks and targeted workspace launch test only.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import { execFileSync } from "node:child_process";
5+
import { fileURLToPath, pathToFileURL } from "node:url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const repoRoot = path.resolve(__dirname, "..", "..");
10+
const workspaceHtmlPath = path.join(repoRoot, "tools", "workspace-v2", "index.html");
11+
const workspaceJsPath = path.join(repoRoot, "tools", "workspace-v2", "index.js");
12+
const assetHtmlPath = path.join(repoRoot, "tools", "asset-browser-v2", "index.html");
13+
const assetJsPath = path.join(repoRoot, "tools", "asset-browser-v2", "index.js");
14+
const toolsIndexPath = path.join(repoRoot, "tools", "index.html");
15+
const resultsPath = path.join(repoRoot, "tmp", "pr_11_313_workspace_asset_manager_launch_results.json");
16+
17+
function readText(filePath) {
18+
return fs.readFileSync(filePath, "utf8");
19+
}
20+
21+
function checkSyntax(filePath) {
22+
execFileSync(process.execPath, ["--check", filePath], {
23+
cwd: repoRoot,
24+
stdio: ["ignore", "pipe", "pipe"]
25+
});
26+
}
27+
28+
export function run() {
29+
checkSyntax(workspaceJsPath);
30+
checkSyntax(assetJsPath);
31+
32+
const workspaceHtml = readText(workspaceHtmlPath);
33+
const workspaceJs = readText(workspaceJsPath);
34+
const assetHtml = readText(assetHtmlPath);
35+
const assetJs = readText(assetJsPath);
36+
const toolsIndex = readText(toolsIndexPath);
37+
38+
const summary = {
39+
generatedAt: new Date().toISOString(),
40+
checks: {
41+
workspaceHasToolsSection: workspaceHtml.includes("<h2>Tools</h2>"),
42+
workspaceHasAssetManagerButton: workspaceHtml.includes('id="workspaceV2OpenAssetManagerButton"'),
43+
workspaceShowsAssetManagerLabel: workspaceHtml.includes(">Asset Manager V2<"),
44+
workspaceWiresAssetManagerButton: workspaceJs.includes('this.openAssetManagerButton = document.getElementById("workspaceV2OpenAssetManagerButton");') &&
45+
workspaceJs.includes("this.openAssetManagerButton.addEventListener(\"click\", () => {"),
46+
workspaceHasAssetManagerLaunchPath: workspaceJs.includes("openAssetManagerFromWorkspace()") &&
47+
workspaceJs.includes("toolId: \"asset-browser-v2\"") &&
48+
workspaceJs.includes("payloadJson: this.cloneSessionValue(this.currentSessionPayload.payloadJson)") &&
49+
workspaceJs.includes("this.buildToolLaunchUrl(\"asset-browser-v2\", hostContextId)"),
50+
assetManagerUiTitle: assetHtml.includes("<title>Asset Manager V2</title>") &&
51+
assetJs.includes("document.title = \"Asset Manager V2\";"),
52+
assetManagerKeepsToolIdContract: assetHtml.includes('data-tool-id="asset-browser-v2"') &&
53+
assetJs.includes("versionCheck.payload.toolId.trim() !== \"asset-browser-v2\""),
54+
toolsIndexShowsAssetManager: toolsIndex.includes(">Asset Manager V2<")
55+
}
56+
};
57+
58+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
59+
fs.writeFileSync(resultsPath, `${JSON.stringify(summary, null, 2)}\n`, "utf8");
60+
61+
for (const [key, value] of Object.entries(summary.checks)) {
62+
assert.equal(value, true, `${key} failed`);
63+
}
64+
65+
return summary;
66+
}
67+
68+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
69+
try {
70+
const summary = run();
71+
console.log(JSON.stringify(summary, null, 2));
72+
} catch (error) {
73+
console.error(error);
74+
process.exitCode = 1;
75+
}
76+
}

tools/asset-browser-v2/index.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>Asset Browser V2</title>
6+
<title>Asset Manager V2</title>
77
<link rel="stylesheet" href="../../src/engine/theme/main.css" />
88
<link rel="stylesheet" href="../../src/engine/ui/hubCommon.css" />
99
</head>
@@ -12,28 +12,28 @@
1212
<main class="page-shell">
1313
<section class="content-section">
1414
<details class="is-collapsible" open>
15-
<summary class="is-collapsible__summary">Asset Browser V2 Session</summary>
15+
<summary class="is-collapsible__summary">Asset Manager V2 Session</summary>
1616
<div class="is-collapsible__content">
1717
<p id="assetBrowserV2SessionReadout">Waiting for session context.</p>
1818
<div>
19-
<p id="assetBrowserV2Breadcrumb">Workspace V2 -> Asset Browser V2</p>
19+
<p id="assetBrowserV2Breadcrumb">Workspace V2 -> Asset Manager V2</p>
2020
<button id="assetBrowserV2BackButton" type="button">Back</button>
2121
</div>
2222
<div class="asset-browser-v2-grid">
2323
<aside class="asset-browser-v2-panel" data-menu-tool>
2424
<h3>menuTool</h3>
2525
<p id="assetBrowserV2ContractReadout">payloadJson.assetCatalog not loaded.</p>
2626
<button id="assetBrowserV2OpenSvgAssetStudioV2Button" type="button">Open in SVG Asset Studio V2</button>
27-
<div id="assetBrowserV2List" aria-label="Asset Browser V2 asset list"></div>
27+
<div id="assetBrowserV2List" aria-label="Asset Manager V2 asset list"></div>
2828
</aside>
2929
<section class="asset-browser-v2-panel" aria-live="polite">
3030
<h3 id="assetBrowserV2Title">No assets loaded</h3>
3131
<span id="assetBrowserV2Count" class="badge">0 assets</span>
32-
<div id="assetBrowserV2EmptyState">No asset catalog session data found. Open Asset Browser V2 with a valid hostContextId session.</div>
33-
<div id="assetBrowserV2InvalidState" hidden>Asset Browser V2 session data is invalid. Re-open the tool from a valid host session.</div>
32+
<div id="assetBrowserV2EmptyState">No asset catalog session data found. Open Asset Manager V2 with a valid hostContextId session.</div>
33+
<div id="assetBrowserV2InvalidState" hidden>Asset Manager V2 session data is invalid. Re-open the tool from a valid host session.</div>
3434
<div id="assetBrowserV2ValidState" hidden>
35-
<div id="assetBrowserV2State">Asset Browser V2 loaded the session asset catalog.</div>
36-
<pre id="assetBrowserV2Preview" aria-label="Asset Browser V2 preview"></pre>
35+
<div id="assetBrowserV2State">Asset Manager V2 loaded the session asset catalog.</div>
36+
<pre id="assetBrowserV2Preview" aria-label="Asset Manager V2 preview"></pre>
3737
</div>
3838
</section>
3939
<aside class="asset-browser-v2-panel" data-menu-workspace>

tools/asset-browser-v2/index.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class AssetBrowserV2 {
22
constructor() {
33
console.log("[AssetBrowserV2]");
44
this.sessionPayloadBytesLimit = 1024 * 1024;
5-
document.title = "Asset Browser V2";
5+
document.title = "Asset Manager V2";
66
document.body.dataset.toolId = "asset-browser-v2";
77
this.urlState = this.readUrlState();
88
this.goBack = this.goBack.bind(this);
@@ -24,7 +24,7 @@ class AssetBrowserV2 {
2424

2525
openSvgAssetStudioV2() {
2626
if (!this.urlState.hostContextId) {
27-
this.renderMissing("No hostContextId is available for launch. Re-open Asset Browser V2 from a valid Tool V2 session link.");
27+
this.renderMissing("No hostContextId is available for launch. Re-open Asset Manager V2 from a valid Tool V2 session link.");
2828
return;
2929
}
3030
const targetUrl = this.buildToolUrl("svg-asset-studio-v2");
@@ -51,7 +51,7 @@ class AssetBrowserV2 {
5151
}
5252

5353
toolLabel(toolId) {
54-
if (toolId === "asset-browser-v2") return "Asset Browser V2";
54+
if (toolId === "asset-browser-v2") return "Asset Manager V2";
5555
if (toolId === "palette-manager-v2") return "Palette Manager V2";
5656
if (toolId === "svg-asset-studio-v2") return "SVG Asset Studio V2";
5757
if (toolId === "tilemap-studio-v2") return "Tilemap Studio V2";
@@ -62,7 +62,7 @@ class AssetBrowserV2 {
6262

6363
renderNavigation() {
6464
const sourceLabel = this.toolLabel(this.urlState.fromTool) || "Workspace V2";
65-
document.getElementById("assetBrowserV2Breadcrumb").textContent = `Workspace V2 -> ${sourceLabel} -> Asset Browser V2`;
65+
document.getElementById("assetBrowserV2Breadcrumb").textContent = `Workspace V2 -> ${sourceLabel} -> Asset Manager V2`;
6666
document.getElementById("assetBrowserV2BackButton").textContent = `Back to ${sourceLabel}`;
6767
}
6868

@@ -129,7 +129,7 @@ class AssetBrowserV2 {
129129
console.log("[SESSION_CONTEXT_READ]");
130130
try {
131131
if (!this.urlState.hostContextId) {
132-
this.renderMissing("No hostContextId was provided. Re-open Asset Browser V2 from a valid Tool V2 session link.");
132+
this.renderMissing("No hostContextId was provided. Re-open Asset Manager V2 from a valid Tool V2 session link.");
133133
return;
134134
}
135135
const serializedSession = window.sessionStorage.getItem(
@@ -138,7 +138,7 @@ class AssetBrowserV2 {
138138
if (
139139
!serializedSession
140140
) {
141-
this.renderMissing("No session data was found for the provided hostContextId. Re-open Asset Browser V2 from the tools index or a host flow that creates the session context first.");
141+
this.renderMissing("No session data was found for the provided hostContextId. Re-open Asset Manager V2 from the tools index or a host flow that creates the session context first.");
142142
return;
143143
}
144144
if (serializedSession.length > this.sessionPayloadBytesLimit) {
@@ -149,7 +149,7 @@ class AssetBrowserV2 {
149149
JSON.parse(serializedSession)
150150
);
151151
} catch (error) {
152-
const runtimeMessage = `Unable to read Asset Browser V2 session context: ${error instanceof Error ? error.message : "unknown error"}`;
152+
const runtimeMessage = `Unable to read Asset Manager V2 session context: ${error instanceof Error ? error.message : "unknown error"}`;
153153
this.logStructuredError("RUNTIME", runtimeMessage, { hostContextId: this.urlState.hostContextId || "" });
154154
this.renderError(runtimeMessage);
155155
}
@@ -167,31 +167,31 @@ class AssetBrowserV2 {
167167
return;
168168
}
169169
if (typeof versionCheck.payload.toolId !== "string" || versionCheck.payload.toolId.trim() !== "asset-browser-v2") {
170-
this.renderError("Asset Browser V2 session data is invalid. Expected toolId 'asset-browser-v2'.");
170+
this.renderError("Asset Manager V2 session data is invalid. Expected toolId 'asset-browser-v2'.");
171171
return;
172172
}
173173
if (!versionCheck.payload.payloadJson || typeof versionCheck.payload.payloadJson !== "object" || Array.isArray(versionCheck.payload.payloadJson)) {
174-
this.renderError("Asset Browser V2 session data is invalid. Expected payloadJson only.");
174+
this.renderError("Asset Manager V2 session data is invalid. Expected payloadJson only.");
175175
return;
176176
}
177177
if (!versionCheck.payload.payloadJson.assetCatalog || typeof versionCheck.payload.payloadJson.assetCatalog !== "object" || Array.isArray(versionCheck.payload.payloadJson.assetCatalog)) {
178-
this.renderError("Asset Browser V2 session data is invalid. Expected payloadJson.assetCatalog.");
178+
this.renderError("Asset Manager V2 session data is invalid. Expected payloadJson.assetCatalog.");
179179
return;
180180
}
181181
if (typeof versionCheck.payload.payloadJson.importName === "string" || typeof versionCheck.payload.payloadJson.importDestination === "string") {
182-
this.renderError("Asset Browser V2 session data is invalid. Remove payloadJson.importName/importDestination. Load assets from payloadJson.assetCatalog only.");
182+
this.renderError("Asset Manager V2 session data is invalid. Remove payloadJson.importName/importDestination. Load assets from payloadJson.assetCatalog only.");
183183
return;
184184
}
185185
this.renderCatalog(versionCheck.payload.payloadJson.assetCatalog, versionCheck.payload);
186186
}
187187

188188
renderCatalog(assetCatalog, sessionContext) {
189189
if (typeof assetCatalog.name !== "string" || !assetCatalog.name.trim()) {
190-
this.renderError("Asset Browser V2 session data is invalid. Expected assetCatalog.name.");
190+
this.renderError("Asset Manager V2 session data is invalid. Expected assetCatalog.name.");
191191
return;
192192
}
193193
if (!Array.isArray(assetCatalog.entries)) {
194-
this.renderError("Asset Browser V2 session data is invalid. Expected assetCatalog.entries[].");
194+
this.renderError("Asset Manager V2 session data is invalid. Expected assetCatalog.entries[].");
195195
return;
196196
}
197197
if (
@@ -210,7 +210,7 @@ class AssetBrowserV2 {
210210
!entry.path.trim()
211211
)
212212
) {
213-
this.renderError("Asset Browser V2 session data is invalid. Every entry requires id, label, kind, and path.");
213+
this.renderError("Asset Manager V2 session data is invalid. Every entry requires id, label, kind, and path.");
214214
return;
215215
}
216216

@@ -220,10 +220,10 @@ class AssetBrowserV2 {
220220
document.getElementById("assetBrowserV2Title").textContent = assetCatalog.name.trim();
221221
document.getElementById("assetBrowserV2Count").textContent = `${assetCatalog.entries.length} asset${assetCatalog.entries.length === 1 ? "" : "s"}`;
222222
document.getElementById("assetBrowserV2State").textContent = assetCatalog.entries.length === 0
223-
? "Asset Browser V2 loaded a valid session asset catalog with zero entries."
224-
: "Asset Browser V2 loaded the session asset catalog.";
223+
? "Asset Manager V2 loaded a valid session asset catalog with zero entries."
224+
: "Asset Manager V2 loaded the session asset catalog.";
225225
if (assetCatalog.entries.length === 0) {
226-
document.getElementById("assetBrowserV2EmptyState").textContent = "Asset catalog is valid but empty. Add assets to payloadJson.assetCatalog.entries and relaunch Asset Browser V2.";
226+
document.getElementById("assetBrowserV2EmptyState").textContent = "Asset catalog is valid but empty. Add assets to payloadJson.assetCatalog.entries and relaunch Asset Manager V2.";
227227
document.getElementById("assetBrowserV2EmptyState").hidden = false;
228228
} else {
229229
document.getElementById("assetBrowserV2EmptyState").hidden = true;
@@ -279,10 +279,10 @@ class AssetBrowserV2 {
279279
this.logStructuredError("INVALID", message, { hostContextId: this.urlState.hostContextId || "" });
280280
document.getElementById("assetBrowserV2SessionReadout").textContent = "Session: read attempted";
281281
document.getElementById("assetBrowserV2ContractReadout").textContent = "payloadJson.assetCatalog invalid";
282-
document.getElementById("assetBrowserV2WorkspaceReadout").textContent = "Workspace writes are disabled for invalid Asset Browser V2 session data.";
283-
document.getElementById("assetBrowserV2Title").textContent = "Asset Browser V2 error";
282+
document.getElementById("assetBrowserV2WorkspaceReadout").textContent = "Workspace writes are disabled for invalid Asset Manager V2 session data.";
283+
document.getElementById("assetBrowserV2Title").textContent = "Asset Manager V2 error";
284284
document.getElementById("assetBrowserV2Count").textContent = "0 assets";
285-
document.getElementById("assetBrowserV2InvalidState").textContent = `${message} Re-open Asset Browser V2 from a host session that provides payloadJson.assetCatalog.`;
285+
document.getElementById("assetBrowserV2InvalidState").textContent = `${message} Re-open Asset Manager V2 from a host session that provides payloadJson.assetCatalog.`;
286286
document.getElementById("assetBrowserV2EmptyState").hidden = true;
287287
document.getElementById("assetBrowserV2InvalidState").hidden = false;
288288
document.getElementById("assetBrowserV2ValidState").hidden = true;

0 commit comments

Comments
 (0)