Skip to content

Commit 86bcddf

Browse files
author
DavidQ
committed
Add asset add/remove functionality with strict validation and persistence in Asset Manager V2 - PR_11_315
1 parent 3f71bac commit 86bcddf

7 files changed

Lines changed: 209 additions & 2 deletions

File tree

docs/dev/codex_commands.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,10 @@ PR_11_314
145145
```bash
146146
npx @openai/codex run --model gpt-5.3-codex --reasoning medium "Implement PR_11_314: persist valid Asset Manager V2 payloadJson.assetCatalog into Workspace V2 active session/export path and block invalid writes."
147147
```
148+
149+
---
150+
PR_11_315
151+
152+
```bash
153+
npx @openai/codex run --model gpt-5.3-codex --reasoning medium "Implement PR_11_315: Enable core asset management actions in Asset Manager V2 (add/remove entries) with strict required-field validation and workspace persistence."
154+
```

docs/dev/commit_comment.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Persist valid Asset Manager V2 assetCatalog into Workspace V2 activeSession export path and block invalid write-back - PR 11.314
1+
Enable Asset Manager V2 add/remove asset entries with strict validation and workspace persistence - PR 11.315
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# PR_11_315 Report
2+
3+
## Purpose
4+
Enable core add/remove asset actions in Asset Manager V2 with strict entry validation and persistence through the Workspace export path.
5+
6+
## Files Changed
7+
- `tools/asset-manager-v2/index.html`
8+
- `tools/asset-manager-v2/index.js`
9+
- `docs/pr/PR_11_315_ASSET_MANAGER_CORE_ADD_REMOVE/PLAN_PR.md`
10+
- `docs/pr/PR_11_315_ASSET_MANAGER_CORE_ADD_REMOVE/BUILD_PR.md`
11+
- `docs/dev/reports/PR_11_315_report.md`
12+
- `docs/dev/codex_commands.md`
13+
- `docs/dev/commit_comment.txt`
14+
15+
## Implementation Summary
16+
- Added add form UI with required fields (`id`, `label`, `kind`, `path`).
17+
- Added strict add validation:
18+
- all fields required
19+
- no duplicate `id`
20+
- no add when session is invalid/missing
21+
- Added remove action per asset row:
22+
- removes by `id`
23+
- blocks with clear status when id is missing/not found
24+
- After add/remove:
25+
- updated catalog is revalidated
26+
- valid session payload is re-persisted for Workspace V2 export path
27+
28+
## Validation Commands
29+
- `node --check tools/asset-manager-v2/index.js` -> **PASS**
30+
- legacy-id content search -> **PASS** (zero matches)
31+
- legacy-id path search -> **PASS** (zero matches)
32+
33+
## Notes
34+
- No schema updates.
35+
- No fallback/default asset injection.
36+
- No alias support added.
37+
- Full samples smoke test was **skipped** because this PR only changes one tool (`asset-manager-v2`) and is fully covered by targeted syntax validation.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# BUILD_PR_11_315
2+
3+
## Implementation
4+
- Added minimal Asset Manager V2 add form in `menuTool`:
5+
- `id`
6+
- `label`
7+
- `kind`
8+
- `path`
9+
- `Add Asset` button
10+
- action status readout
11+
- Implemented strict add validation in `tools/asset-manager-v2/index.js`:
12+
- requires non-empty `id`, `label`, `kind`, `path`
13+
- rejects duplicate `id`
14+
- rejects add when no valid session is loaded
15+
- Implemented remove action per asset row:
16+
- remove by `id`
17+
- blocks when id not found or session is invalid
18+
- Add/remove operations update `payloadJson.assetCatalog.entries` and re-run contract validation/render path.
19+
- Updated valid state to keep Workspace persistence through existing write-back path so exports reflect add/remove updates.
20+
21+
## Validation
22+
- `node --check tools/asset-manager-v2/index.js`
23+
- legacy-id content audit -> zero matches
24+
- legacy-id path audit -> zero matches
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# PLAN_PR_11_315
2+
3+
## Purpose
4+
Enable core asset management actions in Asset Manager V2 by adding/removing entries in `payloadJson.assetCatalog.entries`.
5+
6+
## Scope
7+
- `tools/asset-manager-v2/index.html`
8+
- `tools/asset-manager-v2/index.js`
9+
- `docs/dev/reports/PR_11_315_report.md`
10+
- `docs/dev/codex_commands.md`
11+
- `docs/dev/commit_comment.txt`
12+
13+
## Steps
14+
1. Add minimal add-asset form UI (`id`, `label`, `kind`, `path`) in Asset Manager V2.
15+
2. Validate add inputs strictly (all required fields; no guessed values).
16+
3. Add remove-by-id action per asset row.
17+
4. Persist updated valid catalog through existing Workspace V2 session write-back path.
18+
5. Keep invalid payloads blocked from write-back.
19+
6. Run targeted syntax and legacy-id zero-occurrence checks.

tools/asset-manager-v2/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@
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>
28+
<h4>Add Asset</h4>
29+
<label for="assetManagerV2AddId">id</label>
30+
<input id="assetManagerV2AddId" type="text" />
31+
<label for="assetManagerV2AddLabel">label</label>
32+
<input id="assetManagerV2AddLabel" type="text" />
33+
<label for="assetManagerV2AddKind">kind</label>
34+
<input id="assetManagerV2AddKind" type="text" />
35+
<label for="assetManagerV2AddPath">path</label>
36+
<input id="assetManagerV2AddPath" type="text" />
37+
<button id="assetManagerV2AddButton" type="button">Add Asset</button>
38+
<p id="assetManagerV2ActionStatus">No asset action yet.</p>
39+
</div>
2740
<div id="assetBrowserV2List" aria-label="Asset Manager V2 asset list"></div>
2841
</aside>
2942
<section class="asset-manager-v2-panel" aria-live="polite">

tools/asset-manager-v2/index.js

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ class AssetBrowserV2 {
77
this.urlState = this.readUrlState();
88
this.goBack = this.goBack.bind(this);
99
this.openSvgAssetStudioV2 = this.openSvgAssetStudioV2.bind(this);
10+
this.addAssetEntry = this.addAssetEntry.bind(this);
11+
this.currentSessionContext = null;
1012
this.handleNavigationState = this.handleNavigationState.bind(this);
1113
window.addEventListener("popstate", this.handleNavigationState);
1214
window.addEventListener("pageshow", this.handleNavigationState);
1315
document.getElementById("assetBrowserV2BackButton").addEventListener("click", this.goBack);
1416
document.getElementById("assetBrowserV2OpenSvgAssetStudioV2Button").addEventListener("click", this.openSvgAssetStudioV2);
17+
document.getElementById("assetManagerV2AddButton").addEventListener("click", this.addAssetEntry);
1518
this.renderNavigation();
1619
this.registerSnapshotHook();
1720
this.readSession();
@@ -116,6 +119,97 @@ class AssetBrowserV2 {
116119
window.__v2RuntimeSnapshot = () => this.buildRuntimeSnapshot();
117120
}
118121

122+
cloneSessionValue(value) {
123+
return JSON.parse(JSON.stringify(value));
124+
}
125+
126+
setActionStatus(message) {
127+
document.getElementById("assetManagerV2ActionStatus").textContent = message;
128+
}
129+
130+
normalizedAssetEntryFromForm() {
131+
const id = typeof document.getElementById("assetManagerV2AddId").value === "string" ? document.getElementById("assetManagerV2AddId").value.trim() : "";
132+
const label = typeof document.getElementById("assetManagerV2AddLabel").value === "string" ? document.getElementById("assetManagerV2AddLabel").value.trim() : "";
133+
const kind = typeof document.getElementById("assetManagerV2AddKind").value === "string" ? document.getElementById("assetManagerV2AddKind").value.trim() : "";
134+
const path = typeof document.getElementById("assetManagerV2AddPath").value === "string" ? document.getElementById("assetManagerV2AddPath").value.trim() : "";
135+
if (!id || !label || !kind || !path) {
136+
return { ok: false, message: "Add blocked. id, label, kind, and path are required.", entry: null };
137+
}
138+
return { ok: true, message: "", entry: { id, label, kind, path } };
139+
}
140+
141+
clearAddAssetForm() {
142+
document.getElementById("assetManagerV2AddId").value = "";
143+
document.getElementById("assetManagerV2AddLabel").value = "";
144+
document.getElementById("assetManagerV2AddKind").value = "";
145+
document.getElementById("assetManagerV2AddPath").value = "";
146+
}
147+
148+
addAssetEntry() {
149+
if (!this.currentSessionContext || typeof this.currentSessionContext !== "object" || Array.isArray(this.currentSessionContext)) {
150+
this.setActionStatus("Add blocked. No valid Asset Manager V2 session is loaded.");
151+
return;
152+
}
153+
if (!this.currentSessionContext.payloadJson || typeof this.currentSessionContext.payloadJson !== "object" || Array.isArray(this.currentSessionContext.payloadJson)) {
154+
this.setActionStatus("Add blocked. payloadJson is missing.");
155+
return;
156+
}
157+
if (!this.currentSessionContext.payloadJson.assetCatalog || typeof this.currentSessionContext.payloadJson.assetCatalog !== "object" || Array.isArray(this.currentSessionContext.payloadJson.assetCatalog)) {
158+
this.setActionStatus("Add blocked. payloadJson.assetCatalog is missing.");
159+
return;
160+
}
161+
if (!Array.isArray(this.currentSessionContext.payloadJson.assetCatalog.entries)) {
162+
this.setActionStatus("Add blocked. payloadJson.assetCatalog.entries must be an array.");
163+
return;
164+
}
165+
const newEntry = this.normalizedAssetEntryFromForm();
166+
if (!newEntry.ok) {
167+
this.setActionStatus(newEntry.message);
168+
return;
169+
}
170+
if (this.currentSessionContext.payloadJson.assetCatalog.entries.some((entry) => entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.id === "string" && entry.id.trim() === newEntry.entry.id)) {
171+
this.setActionStatus(`Add blocked. Asset id '${newEntry.entry.id}' already exists.`);
172+
return;
173+
}
174+
const nextSessionContext = this.cloneSessionValue(this.currentSessionContext);
175+
nextSessionContext.payloadJson.assetCatalog.entries.push(newEntry.entry);
176+
this.loadContract(nextSessionContext);
177+
this.clearAddAssetForm();
178+
this.setActionStatus(`Asset '${newEntry.entry.id}' added.`);
179+
}
180+
181+
removeAssetEntryById(assetId) {
182+
if (!this.currentSessionContext || typeof this.currentSessionContext !== "object" || Array.isArray(this.currentSessionContext)) {
183+
this.setActionStatus("Remove blocked. No valid Asset Manager V2 session is loaded.");
184+
return;
185+
}
186+
if (!this.currentSessionContext.payloadJson || typeof this.currentSessionContext.payloadJson !== "object" || Array.isArray(this.currentSessionContext.payloadJson)) {
187+
this.setActionStatus("Remove blocked. payloadJson is missing.");
188+
return;
189+
}
190+
if (!this.currentSessionContext.payloadJson.assetCatalog || typeof this.currentSessionContext.payloadJson.assetCatalog !== "object" || Array.isArray(this.currentSessionContext.payloadJson.assetCatalog)) {
191+
this.setActionStatus("Remove blocked. payloadJson.assetCatalog is missing.");
192+
return;
193+
}
194+
if (!Array.isArray(this.currentSessionContext.payloadJson.assetCatalog.entries)) {
195+
this.setActionStatus("Remove blocked. payloadJson.assetCatalog.entries must be an array.");
196+
return;
197+
}
198+
if (typeof assetId !== "string" || !assetId.trim()) {
199+
this.setActionStatus("Remove blocked. Asset id is required.");
200+
return;
201+
}
202+
const nextSessionContext = this.cloneSessionValue(this.currentSessionContext);
203+
const startLength = nextSessionContext.payloadJson.assetCatalog.entries.length;
204+
nextSessionContext.payloadJson.assetCatalog.entries = nextSessionContext.payloadJson.assetCatalog.entries.filter((entry) => !entry || typeof entry !== "object" || Array.isArray(entry) || typeof entry.id !== "string" || entry.id.trim() !== assetId.trim());
205+
if (nextSessionContext.payloadJson.assetCatalog.entries.length === startLength) {
206+
this.setActionStatus(`Remove blocked. Asset id '${assetId.trim()}' was not found.`);
207+
return;
208+
}
209+
this.loadContract(nextSessionContext);
210+
this.setActionStatus(`Asset '${assetId.trim()}' removed.`);
211+
}
212+
119213
logStructuredError(type, message, details) {
120214
console.error({
121215
tool: "asset-manager-v2",
@@ -270,16 +364,21 @@ class AssetBrowserV2 {
270364
}
271365
document.getElementById("assetBrowserV2InvalidState").hidden = true;
272366
document.getElementById("assetBrowserV2ValidState").hidden = false;
367+
this.currentSessionContext = this.cloneSessionValue(sessionContext);
273368

274369
document.getElementById("assetBrowserV2List").replaceChildren();
275370
assetCatalog.entries.forEach((entry) => {
371+
const assetRow = document.createElement("div");
276372
const assetItem = document.createElement("button");
277373
const assetName = document.createElement("strong");
278374
const assetMeta = document.createElement("div");
375+
const removeButton = document.createElement("button");
279376
assetItem.type = "button";
377+
removeButton.type = "button";
280378
assetName.textContent = entry.label.trim();
281379
assetMeta.textContent = `${entry.kind.trim()} | ${entry.path.trim()}`;
282380
assetItem.append(assetName, assetMeta);
381+
removeButton.textContent = `Remove ${entry.id.trim()}`;
283382
assetItem.addEventListener("click", () => {
284383
document.getElementById("assetBrowserV2Preview").textContent = JSON.stringify(
285384
{
@@ -292,7 +391,11 @@ class AssetBrowserV2 {
292391
2
293392
);
294393
});
295-
document.getElementById("assetBrowserV2List").appendChild(assetItem);
394+
removeButton.addEventListener("click", () => {
395+
this.removeAssetEntryById(entry.id.trim());
396+
});
397+
assetRow.append(assetItem, removeButton);
398+
document.getElementById("assetBrowserV2List").appendChild(assetRow);
296399
});
297400

298401
document.getElementById("assetBrowserV2Preview").textContent = assetCatalog.entries.length === 0
@@ -313,6 +416,8 @@ class AssetBrowserV2 {
313416
document.getElementById("assetBrowserV2ValidState").hidden = true;
314417
document.getElementById("assetBrowserV2List").replaceChildren();
315418
document.getElementById("assetBrowserV2Preview").textContent = "";
419+
this.currentSessionContext = null;
420+
this.setActionStatus("No asset action yet.");
316421
}
317422

318423
renderError(message) {
@@ -328,6 +433,8 @@ class AssetBrowserV2 {
328433
document.getElementById("assetBrowserV2ValidState").hidden = true;
329434
document.getElementById("assetBrowserV2List").replaceChildren();
330435
document.getElementById("assetBrowserV2Preview").textContent = "";
436+
this.currentSessionContext = null;
437+
this.setActionStatus("No asset action yet.");
331438
}
332439
}
333440

0 commit comments

Comments
 (0)