Skip to content

Commit 1f12b0c

Browse files
author
DavidQ
committed
Clean Object Transform display runtime tag validation and hub spacing - PR_26133_041-object-transform-summary-and-runtime-validation-cleanup
1 parent dbbcc3a commit 1f12b0c

9 files changed

Lines changed: 119 additions & 120 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_040 Playwright V8 Coverage Report
1+
# PR_26133_041 Playwright V8 Coverage Report
22

3-
Task: PR_26133_040-object-transform-one-line-control-layout
3+
Task: PR_26133_041-object-transform-summary-and-runtime-validation-cleanup
44
Date: 2026-05-15
55

66
## Result
@@ -26,15 +26,20 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
2626
## Changed Runtime JS Coverage
2727

2828
```text
29-
(94%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 4709/4709; executed functions 487/516
29+
(94%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 4733/4733; executed functions 488/517
30+
(82%) games/Asteroids/game/asteroidsObjectVectorRoles.js - executed lines 176/176; executed functions 14/17
3031
```
3132

3233
## Changed Test Coverage Note
3334

3435
```text
36+
(0%) tests/games/AsteroidsAssetReferenceAdoption.test.mjs - changed JS file not collected as browser runtime coverage
37+
(0%) tests/games/AsteroidsPlatformDemo.test.mjs - changed JS file not collected as browser runtime coverage
3538
(0%) tests/playwright/tools/WorkspaceManagerV2.spec.mjs - changed JS file not collected as browser runtime coverage
3639
```
3740

41+
CSS changes in `tools/object-vector-studio-v2/styles/toolStarter.css` and `src/engine/ui/hubCommon.css` are verified through Playwright DOM/CSS assertions rather than V8 JavaScript coverage.
42+
3843
## Guardrail
3944

4045
```text
@@ -43,4 +48,4 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
4348

4449
## PR-Specific Note
4550

46-
The Workspace V2 run exercised Object Vector Studio V2 one-line Move, Origin, Rotate, and Scale transform controls, transform summary rendering, preserved transform actions, and Object Transform no-console-error paths. Coverage remains advisory only.
51+
The Workspace V2 run exercised Object Vector Studio V2 Scale input styling, singular transform summary formatting, Rotate normalization, Object Geometry Delete Point(s) labeling, Asteroids runtime binding validation without object tag checks, and hub CSS spacing assertions. Coverage remains advisory only.
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_040 Workspace V2 Playwright Results
1+
# PR_26133_041 Workspace V2 Playwright Results
22

3-
Task: PR_26133_040-object-transform-one-line-control-layout
3+
Task: PR_26133_041-object-transform-summary-and-runtime-validation-cleanup
44
Date: 2026-05-15
55

66
## Result
@@ -14,13 +14,14 @@ PASS - `npm run test:workspace-v2` completed successfully.
1414

1515
## PR-Specific Coverage
1616

17-
- Verified Object Transform Move controls render on one line as `Move X [input] Y [input] [Move]`.
18-
- Verified Object Transform Origin controls render on one line as `Origin X [input] Y [input] [Apply]`.
19-
- Verified Object Transform Rotate controls render on one line as `Rotate [input] [Rotate]`.
20-
- Verified existing Scale controls remain one line as `Scale [--] [-] [scale input] [+] [++] [Resize]`.
21-
- Verified Move, Origin Apply, and Rotate buttons continue to update the selected shape transform.
22-
- Verified transform summary is horizontally centered and no longer starts with `Transform`.
17+
- Verified Object Transform Scale input renders with spinner-removal CSS (`appearance: textfield` and WebKit spin-button removal rule).
18+
- Verified transform summary renders singular same-axis scale text such as `x 0, y 0, rot 0, scale 1`.
19+
- Verified Rotate input constrains to `min=0` and `max=360`, normalizes out-of-range input before applying, and writes the normalized value back to the textbox.
20+
- Verified Object Geometry polygon action label renders `Delete Point(s)`.
21+
- Verified Asteroids runtime binding validation succeeds when object `tags` are removed from the runtime payload, keeping tags as Object Vector Studio V2 editor metadata rather than runtime validation data.
22+
- Verified hub page tool section margin resolves to `12px`.
23+
- Targeted Node validation also passed for `AsteroidsAssetReferenceAdoption` and `AsteroidsPlatformDemo`.
2324

2425
## Manual Verification Equivalent
2526

26-
Targeted Object Vector Studio V2 browser automation covered the requested transform row layouts, preserved transform actions, centered summary text, retained Scale row layout, and no-console-error checks.
27+
Targeted Object Vector Studio V2 browser automation covered the requested Scale input styling, transform summary formatting, Rotate normalization, Delete Point(s) label, Asteroids runtime tag-validation cleanup, hub spacing contract, and no-console-error checks.

games/Asteroids/game/asteroidsObjectVectorRoles.js

Lines changed: 4 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
11
export const ASTEROIDS_RUNTIME_OBJECT_ROLES = Object.freeze({
22
ship: Object.freeze({
33
label: 'Ship',
4-
tags: Object.freeze(['player', 'ship']),
54
}),
65
asteroidLarge: Object.freeze({
76
label: 'Large Asteroid',
87
size: 3,
9-
tags: Object.freeze(['asteroid', 'large']),
108
}),
119
asteroidMedium: Object.freeze({
1210
label: 'Medium Asteroid',
1311
size: 2,
14-
tags: Object.freeze(['asteroid', 'medium']),
1512
}),
1613
asteroidSmall: Object.freeze({
1714
label: 'Small Asteroid',
1815
size: 1,
19-
tags: Object.freeze(['asteroid', 'small']),
2016
}),
2117
ufoLarge: Object.freeze({
2218
label: 'Large UFO',
23-
tags: Object.freeze(['ufo', 'large']),
2419
}),
2520
ufoSmall: Object.freeze({
2621
label: 'Small UFO',
27-
tags: Object.freeze(['ufo', 'small']),
2822
}),
2923
});
3024

@@ -50,36 +44,21 @@ function normalizeString(value) {
5044
return String(value || '').trim();
5145
}
5246

53-
function normalizeTag(value) {
47+
function normalizeSearchText(value) {
5448
return normalizeString(value).toLowerCase();
5549
}
5650

57-
function normalizeTags(value) {
58-
return asArray(value).map(normalizeTag).filter(Boolean);
59-
}
60-
61-
function objectTagSet(object) {
62-
return new Set(normalizeTags(object?.tags));
63-
}
64-
65-
function hasTags(object, tags) {
66-
const tagSet = objectTagSet(object);
67-
return normalizeTags(tags).every((tag) => tagSet.has(tag));
68-
}
69-
7051
function oldObjectSignal(object) {
7152
const text = [
7253
object?.id,
7354
object?.name,
74-
...asArray(object?.tags),
75-
].map(normalizeTag).join(' ');
55+
].map(normalizeSearchText).join(' ');
7656
return /(^|[.\s_-])(old|legacy|deprecated|archive|archived|renamed|stale)($|[.\s_-])/.test(text);
7757
}
7858

7959
function candidateLabel(candidate) {
80-
const tags = normalizeTags(candidate.object?.tags).join(',');
8160
const oldLabel = candidate.oldSignal ? ' old-signal' : '';
82-
return `${candidate.object?.id || 'unknown'} tags=[${tags}] index=${candidate.index}${oldLabel}`;
61+
return `${candidate.object?.id || 'unknown'} name="${candidate.object?.name || 'unknown'}" index=${candidate.index}${oldLabel}`;
8362
}
8463

8564
function logResolution(logger, level, message, details = {}) {
@@ -101,15 +80,13 @@ export function runtimeObjectRoleOptions(roleId, runtimeBindings = {}) {
10180
objectId: '',
10281
requireManifestBinding: true,
10382
runtimeRole: roleId,
104-
tags: [],
10583
};
10684
}
10785
const bindings = asRecord(runtimeBindings);
10886
return {
10987
objectId: normalizeString(bindings[roleId]),
11088
requireManifestBinding: true,
11189
runtimeRole: roleId,
112-
tags: [...role.tags],
11390
};
11491
}
11592

@@ -134,7 +111,7 @@ export function resolveAsteroidsObjectVectorRole(objects, roleId, {
134111
object,
135112
oldSignal: oldObjectSignal(object),
136113
}))
137-
.filter((candidate) => candidate.object && hasTags(candidate.object, role.tags));
114+
.filter((candidate) => candidate.object);
138115

139116
if (!explicitObjectId) {
140117
logResolution(
@@ -144,7 +121,6 @@ export function resolveAsteroidsObjectVectorRole(objects, roleId, {
144121
{
145122
candidates: candidates.map(candidateLabel),
146123
objectCount: objectList.length,
147-
tags: [...role.tags],
148124
}
149125
);
150126
return null;
@@ -159,7 +135,6 @@ export function resolveAsteroidsObjectVectorRole(objects, roleId, {
159135
candidates: candidates.map(candidateLabel),
160136
explicitObjectId,
161137
objectCount: objectList.length,
162-
tags: [...role.tags],
163138
}
164139
);
165140
return null;
@@ -173,53 +148,11 @@ export function resolveAsteroidsObjectVectorRole(objects, roleId, {
173148
{
174149
candidates: candidates.map(candidateLabel),
175150
explicitObjectId,
176-
tags: normalizeTags(explicitObject.tags),
177-
}
178-
);
179-
return null;
180-
}
181-
182-
if (!hasTags(explicitObject, role.tags)) {
183-
logResolution(
184-
logger,
185-
'FAIL',
186-
`Asteroids Object Vector runtime role ${roleId} manifest binding ${explicitObjectId} is missing required tags [${role.tags.join(', ')}].`,
187-
{
188-
candidates: candidates.map(candidateLabel),
189-
explicitObjectId,
190-
objectTags: normalizeTags(explicitObject.tags),
191-
requiredTags: [...role.tags],
192-
}
193-
);
194-
return null;
195-
}
196-
197-
if (!candidates.some((candidate) => candidate.object.id === explicitObjectId)) {
198-
logResolution(
199-
logger,
200-
'FAIL',
201-
`Asteroids Object Vector runtime role ${roleId} manifest binding ${explicitObjectId} was not found in tag candidates [${role.tags.join(', ')}].`,
202-
{
203-
candidates: candidates.map(candidateLabel),
204-
explicitObjectId,
205-
requiredTags: [...role.tags],
206151
}
207152
);
208153
return null;
209154
}
210155

211-
if (candidates.length > 1) {
212-
logResolution(
213-
logger,
214-
'WARN',
215-
`Asteroids Object Vector runtime role ${roleId} matched multiple objects by tags [${role.tags.join(', ')}]; using explicit manifest binding ${explicitObjectId}.`,
216-
{
217-
candidates: candidates.map(candidateLabel),
218-
selectedObjectId: explicitObjectId,
219-
}
220-
);
221-
}
222-
223156
return explicitObject;
224157
}
225158

src/engine/ui/hubCommon.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ body.hub-page-tools {
443443
}
444444

445445
.hub-page-tools section {
446-
margin-top: 32px;
446+
margin-top: 12px;
447447
}
448448

449449
.hub-page-tools section:first-of-type {

tests/games/AsteroidsAssetReferenceAdoption.test.mjs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,14 @@ export async function run() {
6363
const bindingValidation = validateAsteroidsRuntimeObjectBindings(payload.objects, runtimeBindings);
6464
assert.equal(bindingValidation.ok, true);
6565
assert.equal(bindingValidation.objectsByRole.asteroidMedium.id, "object.asteroids.medium-asteroid");
66-
assert.equal(bindingValidation.warnings.some((entry) => (
67-
entry.message.includes("matched multiple objects by tags [asteroid, medium]")
68-
&& entry.details.candidates.some((candidate) => candidate.includes("object.asteroids.medium-asteroid-2"))
69-
)), true);
66+
assert.deepEqual(bindingValidation.warnings, []);
7067
const missingMediumBindings = { ...runtimeBindings };
7168
delete missingMediumBindings.asteroidMedium;
7269
const missingBindingValidation = validateAsteroidsRuntimeObjectBindings(payload.objects, missingMediumBindings);
7370
assert.equal(missingBindingValidation.ok, false);
7471
assert.equal(missingBindingValidation.errors.some((entry) => (
7572
entry.message.includes("objectIds.asteroidMedium")
7673
&& entry.details.candidates.some((candidate) => candidate.includes("object.asteroids.medium-asteroid"))
77-
&& entry.details.candidates.some((candidate) => candidate.includes("object.asteroids.medium-asteroid-2"))
7874
)), true);
7975

8076
const recreatedPayload = createPayloadWithRecreatedMediumAsteroid(payload);

tests/games/AsteroidsPlatformDemo.test.mjs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,25 +88,21 @@ export async function run() {
8888
const bindingValidation = validateAsteroidsRuntimeObjectBindings(payload.objects, runtimeBindings);
8989
assert.equal(bindingValidation.ok, true);
9090
assert.equal(bindingValidation.objectsByRole.asteroidMedium.id, 'object.asteroids.medium-asteroid');
91-
assert.equal(bindingValidation.warnings.some((entry) => (
92-
entry.message.includes('matched multiple objects by tags [asteroid, medium]')
93-
&& entry.details.candidates.some((candidate) => candidate.includes('object.asteroids.medium-asteroid-2'))
94-
)), true);
91+
assert.deepEqual(bindingValidation.warnings, []);
9592
const missingMediumBindings = { ...runtimeBindings };
9693
delete missingMediumBindings.asteroidMedium;
9794
const missingBindingValidation = validateAsteroidsRuntimeObjectBindings(payload.objects, missingMediumBindings);
9895
assert.equal(missingBindingValidation.ok, false);
9996
assert.equal(missingBindingValidation.errors.some((entry) => (
10097
entry.message.includes('objectIds.asteroidMedium')
10198
&& entry.details.candidates.some((candidate) => candidate.includes('object.asteroids.medium-asteroid'))
102-
&& entry.details.candidates.some((candidate) => candidate.includes('object.asteroids.medium-asteroid-2'))
10399
)), true);
104100
const invalidBindingValidation = validateAsteroidsRuntimeObjectBindings(payload.objects, {
105101
...runtimeBindings,
106102
asteroidMedium: 'object.asteroids.large-asteroid'
107103
});
108-
assert.equal(invalidBindingValidation.ok, false);
109-
assert.equal(invalidBindingValidation.errors.some((entry) => entry.message.includes('missing required tags [asteroid, medium]')), true);
104+
assert.equal(invalidBindingValidation.ok, true);
105+
assert.equal(invalidBindingValidation.objectsByRole.asteroidMedium.id, 'object.asteroids.large-asteroid');
110106

111107
const recreatedPayload = createPayloadWithRecreatedMediumAsteroid(payload);
112108
const recreatedBindings = {
@@ -147,15 +143,12 @@ export async function run() {
147143
sourceLabel: 'Asteroids recreated medium object-vector payload'
148144
});
149145
const resolvedMedium = runtime.resolveObject(recreatedAssetSet, {
146+
objectId: 'object.asteroids.medium-asteroid-2',
150147
requireManifestBinding: true,
151-
runtimeRole: 'asteroidMedium',
152-
tags: ['asteroid', 'medium']
148+
runtimeRole: 'asteroidMedium'
153149
});
154150
assert.equal(resolvedMedium.id, 'object.asteroids.medium-asteroid-2');
155-
assert.equal(runtime.getDiagnostics().events.some((event) => (
156-
event.level === 'WARN'
157-
&& event.message.includes('using explicit manifest binding object.asteroids.medium-asteroid-2')
158-
)), true);
151+
assert.equal(runtime.getDiagnostics().events.some((event) => event.message.includes('matched multiple objects by tags')), false);
159152

160153
const smallPreview = runtime.createSvgString(assetSet, {
161154
objectId: 'object.asteroids.small-asteroid',

0 commit comments

Comments
 (0)