Skip to content

Commit 87df2dc

Browse files
author
DavidQ
committed
Tune Object Vector Studio tags rotate summary and palette opacity layout - PR_26133_042-object-transform-tags-and-palette-layout-tuning
1 parent 1f12b0c commit 87df2dc

6 files changed

Lines changed: 148 additions & 65 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

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

3-
Task: PR_26133_041-object-transform-summary-and-runtime-validation-cleanup
3+
Task: PR_26133_042-object-transform-tags-and-palette-layout-tuning
44
Date: 2026-05-15
55

66
## Result
@@ -26,19 +26,16 @@ 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 4733/4733; executed functions 488/517
30-
(82%) games/Asteroids/game/asteroidsObjectVectorRoles.js - executed lines 176/176; executed functions 14/17
29+
(94%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 4729/4729; executed functions 488/517
3130
```
3231

33-
## Changed Test Coverage Note
32+
## Changed Markup/Style/Test Coverage Note
3433

3534
```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
3835
(0%) tests/playwright/tools/WorkspaceManagerV2.spec.mjs - changed JS file not collected as browser runtime coverage
3936
```
4037

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.
38+
Markup and CSS changes in `tools/object-vector-studio-v2/index.html` and `tools/object-vector-studio-v2/styles/toolStarter.css` are verified through Playwright DOM/CSS assertions rather than V8 JavaScript coverage.
4239

4340
## Guardrail
4441

@@ -48,4 +45,4 @@ CSS changes in `tools/object-vector-studio-v2/styles/toolStarter.css` and `src/e
4845

4946
## PR-Specific Note
5047

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.
48+
The Workspace V2 run exercised Object Vector Studio V2 tag button/chip sizing, Rotate range/input-preservation and wrapped summary behavior, singular scale summary formatting, and Palette Paint/Stroke/Width plus Fill Op/Stroke Op row layout. Coverage remains advisory only.
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_041 Workspace V2 Playwright Results
1+
# PR_26133_042 Workspace V2 Playwright Results
22

3-
Task: PR_26133_041-object-transform-summary-and-runtime-validation-cleanup
3+
Task: PR_26133_042-object-transform-tags-and-palette-layout-tuning
44
Date: 2026-05-15
55

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

1515
## PR-Specific Coverage
1616

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`.
17+
- Verified Object tag Add button width is `77px`.
18+
- Verified selected object tag chips for `bubba` and `player` render as `77px` buttons below the Add tag input row.
19+
- Verified Rotate input exposes `min=-359` and `max=359`.
20+
- Verified Rotate keeps the user-entered input value while applying the normalized/wrapped transform rotation.
21+
- Verified transform summary wraps large/current rotation values into `0..359` and keeps singular same-axis scale text, for example `x 0, y 0, rot 73, scale 0.77`.
22+
- Verified Palette controls render Paint, Stroke, and Width on the first row, with compact Fill Op and Stroke Op controls on the row below.
2423

2524
## Manual Verification Equivalent
2625

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.
26+
Targeted Object Vector Studio V2 browser automation covered the requested tag widths/layout, Rotate range and input preservation, wrapped transform summary rotation, compact Palette row split, and no-console-error checks.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,11 +1288,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
12881288
noVisibleTagLabel: element.querySelector("label[for='objectVectorStudioV2ObjectTagInput']") === null,
12891289
tagAddText: tagButton.textContent.trim(),
12901290
tagAriaLabel: tagInput.getAttribute("aria-label"),
1291+
tagButtonWidth: Math.round(tagButtonRect.width),
12911292
tagIconMatchesAddObject: Math.round(Number.parseFloat(getComputedStyle(tagButton, "::before").fontSize)) === Math.round(Number.parseFloat(getComputedStyle(addObjectButton, "::before").fontSize)),
12921293
tagInline: Math.abs((tagInputRect.top + tagInputRect.height / 2) - (tagButtonRect.top + tagButtonRect.height / 2)) < 4 && tagInputRect.right <= tagButtonRect.left
12931294
};
12941295
});
1295-
expect(objectPanelLayout).toMatchObject({ actionButtonLabels: ["Add", "Rename", "Dup"], actionsAfterName: true, actionsBeforeTag: true, actionsDirectlyUnderName: true, actionsSingleLine: true, nameInline: true, noVisibleTagLabel: true, tagAddText: "Add", tagAriaLabel: "Object tag", tagIconMatchesAddObject: true, tagInline: true });
1296+
expect(objectPanelLayout).toMatchObject({ actionButtonLabels: ["Add", "Rename", "Dup"], actionsAfterName: true, actionsBeforeTag: true, actionsDirectlyUnderName: true, actionsSingleLine: true, nameInline: true, noVisibleTagLabel: true, tagAddText: "Add", tagAriaLabel: "Object tag", tagButtonWidth: 77, tagIconMatchesAddObject: true, tagInline: true });
12961297
expect(objectPanelLayout.actionButtonMaxHeight).toBeLessThanOrEqual(34);
12971298
await page.locator("#objectVectorStudioV2ObjectNameInput").fill("Blocked Object");
12981299
await page.locator("#objectVectorStudioV2AddObjectButton").click();
@@ -1694,7 +1695,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16941695
id: `object.asteroids.object-${index + 1}`,
16951696
name: index === 0 ? "Asteroids Ship" : `Object ${index + 1}`,
16961697
shapes: [],
1697-
tags: []
1698+
tags: index === 0 ? ["bubba", "player"] : []
16981699
})),
16991700
toolId: "object-vector-studio-v2",
17001701
version: 1
@@ -1720,18 +1721,37 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17201721
await expect(page.locator("#objectVectorStudioV2StrokeWidth")).toHaveValue("2");
17211722
await expect(page.locator("label[for='objectVectorStudioV2StrokeWidth'] span")).not.toHaveText("Stroke Width");
17221723
await expect(page.locator("label[for='objectVectorStudioV2StrokeWidth'] span")).toHaveText("Width");
1723-
const strokeWidthLayout = await page.locator("#objectVectorStudioV2PaletteContent").evaluate((content) => {
1724+
const paletteControlLayout = await page.locator("#objectVectorStudioV2PaletteContent").evaluate((content) => {
1725+
const primaryRow = content.querySelector(".object-vector-studio-v2__palette-primary-row");
1726+
const opacityRow = content.querySelector(".object-vector-studio-v2__palette-opacity-row");
17241727
const paintButton = content.querySelector("#objectVectorStudioV2PaintModeButton").getBoundingClientRect();
17251728
const strokeButton = content.querySelector("#objectVectorStudioV2StrokeModeButton").getBoundingClientRect();
17261729
const widthLabel = content.querySelector("label[for='objectVectorStudioV2StrokeWidth']").getBoundingClientRect();
17271730
const widthInput = content.querySelector("#objectVectorStudioV2StrokeWidth").getBoundingClientRect();
1731+
const fillOpacityLabel = content.querySelector("label[for='objectVectorStudioV2FillOpacity']").getBoundingClientRect();
1732+
const strokeOpacityLabel = content.querySelector("label[for='objectVectorStudioV2StrokeOpacity']").getBoundingClientRect();
1733+
const opacityInputs = Array.from(opacityRow.querySelectorAll("input")).map((input) => input.getBoundingClientRect());
17281734
return {
1729-
inline: [strokeButton, widthLabel].every((rect) => Math.abs((paintButton.top + paintButton.height / 2) - (rect.top + rect.height / 2)) < 4),
1735+
opacityBelowPrimary: fillOpacityLabel.top >= Math.max(paintButton.bottom, strokeButton.bottom, widthLabel.bottom),
1736+
opacityInline: Math.abs((fillOpacityLabel.top + fillOpacityLabel.height / 2) - (strokeOpacityLabel.top + strokeOpacityLabel.height / 2)) < 4,
1737+
opacityInputCompact: opacityInputs.every((rect) => Math.round(rect.width) <= 42),
1738+
opacityLabels: Array.from(opacityRow.querySelectorAll("label > span")).map((label) => label.textContent.trim()),
1739+
primaryInline: [strokeButton, widthLabel].every((rect) => Math.abs((paintButton.top + paintButton.height / 2) - (rect.top + rect.height / 2)) < 4),
1740+
primaryOrder: Array.from(primaryRow.children).map((element) => element.textContent.trim()),
17301741
widthInputCompact: Math.round(widthInput.width) <= 46,
17311742
widthIsRightOfStroke: widthLabel.left >= strokeButton.right
17321743
};
17331744
});
1734-
expect(strokeWidthLayout).toEqual({ inline: true, widthInputCompact: true, widthIsRightOfStroke: true });
1745+
expect(paletteControlLayout).toEqual({
1746+
opacityBelowPrimary: true,
1747+
opacityInline: true,
1748+
opacityInputCompact: true,
1749+
opacityLabels: ["Fill Op", "Stroke Op"],
1750+
primaryInline: true,
1751+
primaryOrder: ["Paint", "Stroke", "Width"],
1752+
widthInputCompact: true,
1753+
widthIsRightOfStroke: true
1754+
});
17351755
await expect(page.locator(".object-vector-studio-v2__palette-sort")).not.toContainText("Sort");
17361756
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue", "Sat", "Bri", "Name"]);
17371757
const swatchState = await page.locator(".object-vector-studio-v2__palette-swatch").evaluateAll((swatches) => swatches.map((swatch) => {
@@ -1770,6 +1790,29 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17701790
await expect(page.locator("#objectVectorStudioV2ObjectsCount")).toHaveText("(18 obj, 0 shapes)");
17711791
await expect(page.locator("#objectVectorStudioV2ObjectDetails")).toContainText("No shape selected");
17721792
await expect(page.locator("#objectVectorStudioV2ObjectPreviewFooter")).toContainText("Object ID: object.asteroids.object-1");
1793+
await expect(page.locator("#objectVectorStudioV2ObjectTagList [data-object-tag]")).toHaveText(["bubba", "player"]);
1794+
const tagChipLayout = await page.locator("#objectVectorStudioV2ObjectContent").evaluate((element) => {
1795+
const tagInput = element.querySelector("#objectVectorStudioV2ObjectTagInput").getBoundingClientRect();
1796+
const addButton = element.querySelector("#objectVectorStudioV2AddTagButton").getBoundingClientRect();
1797+
const chips = Array.from(element.querySelectorAll("#objectVectorStudioV2ObjectTagList [data-object-tag]"));
1798+
const chipRects = chips.map((chip) => chip.getBoundingClientRect());
1799+
return {
1800+
addButtonWidth: Math.round(addButton.width),
1801+
chipTexts: chips.map((chip) => chip.textContent.trim()),
1802+
chipWidths: chipRects.map((rect) => Math.round(rect.width)),
1803+
chipsBelowAddRow: chipRects.every((rect) => rect.top >= addButton.bottom),
1804+
chipsSameRow: chipRects.every((rect) => Math.abs(rect.top - chipRects[0].top) <= 2),
1805+
inputAndAddInline: Math.abs((tagInput.top + tagInput.height / 2) - (addButton.top + addButton.height / 2)) < 4 && tagInput.right <= addButton.left
1806+
};
1807+
});
1808+
expect(tagChipLayout).toEqual({
1809+
addButtonWidth: 77,
1810+
chipTexts: ["bubba", "player"],
1811+
chipWidths: [77, 77],
1812+
chipsBelowAddRow: true,
1813+
chipsSameRow: true,
1814+
inputAndAddInline: true
1815+
});
17731816
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
17741817
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(1);
17751818
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveAttribute("r", "9");
@@ -1944,6 +1987,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
19441987
rowType: "rotate"
19451988
}
19461989
]);
1990+
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveAttribute("min", "-359");
1991+
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveAttribute("max", "359");
19471992
const transformBeforeInvalid = await page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().transform);
19481993
await page.locator("#objectVectorStudioV2MoveXInput").fill("");
19491994
await page.locator("#objectVectorStudioV2MoveYInput").fill("7");
@@ -2284,16 +2329,28 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22842329

22852330
await page.locator("#objectVectorStudioV2AngleSnapButton").click();
22862331
await page.locator("#objectVectorStudioV2RotateInput").fill("22");
2332+
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveValue("22");
22872333
await page.locator("#objectVectorStudioV2RotateShapeButton").click();
22882334
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"rotation": 15');
2289-
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveValue("15");
2335+
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveValue("22");
22902336
await expect(page.locator("#statusLog")).toHaveValue(/OK Rotated shape row 0 by 15 degrees\./);
22912337
await page.locator("#objectVectorStudioV2AngleSnapButton").click();
2292-
await page.locator("#objectVectorStudioV2RotateInput").fill("725");
2338+
await page.locator("#objectVectorStudioV2RotateInput").fill("-30");
2339+
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveValue("-30");
22932340
await page.locator("#objectVectorStudioV2RotateShapeButton").click();
2294-
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"rotation": 20');
2295-
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveValue("5");
2296-
await expect(page.locator("#statusLog")).toHaveValue(/OK Rotated shape row 0 by 5 degrees\./);
2341+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"rotation": 345');
2342+
await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveValue("-30");
2343+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("x 10, y 10, rot 345, scale 1");
2344+
await expect(page.locator("#statusLog")).toHaveValue(/OK Rotated shape row 0 by -30 degrees\./);
2345+
const wrappedLargeRotationSummary = await page.evaluate(() => window.__objectVectorStudioV2App.formatTransformSummary({
2346+
origin: { x: 0, y: 0 },
2347+
rotation: 2233,
2348+
scaleX: 0.77,
2349+
scaleY: 0.77,
2350+
x: 0,
2351+
y: 0
2352+
}));
2353+
expect(wrappedLargeRotationSummary).toBe("x 0, y 0, rot 73, scale 0.77");
22972354
await page.locator("#objectVectorStudioV2OriginXInput").fill("2");
22982355
await page.locator("#objectVectorStudioV2OriginYInput").fill("-3");
22992356
await page.locator("#objectVectorStudioV2ApplyOriginButton").click();
@@ -2309,7 +2366,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23092366
await expect(page.locator("#objectVectorStudioV2ScaleInput")).not.toHaveAttribute("aria-invalid", "true");
23102367
await expect(page.locator("#statusLog")).toHaveValue(/OK Scale preview set to 1\.2 for shape row 0\./);
23112368
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"scaleX": 1.2');
2312-
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("x 10, y 10, rot 20, scale 1.2");
2369+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("x 10, y 10, rot 345, scale 1.2");
23132370
const selectionBeforeScaleStep = await page.locator("#objectVectorStudioV2RenderSurface [data-selection-bounds='0']").evaluate((box) => ({
23142371
height: Number(box.getAttribute("height")),
23152372
width: Number(box.getAttribute("width"))
@@ -2361,7 +2418,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23612418
}
23622419
});
23632420
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1");
2364-
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("x 10, y 10, rot 20, scale 1");
2421+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("x 10, y 10, rot 345, scale 1");
23652422
await expect(page.locator("#statusLog")).toHaveValue(/OK Resize Geometry applied scale 1\.2 to shape row 0; transform scale reset to 1\./);
23662423

23672424
await page.locator("#objectVectorStudioV2BringForwardButton").click();

0 commit comments

Comments
 (0)