Skip to content

Commit 7db3b3c

Browse files
author
DavidQ
committed
Add Square tool and align Shape Tools icon spacing - PR_26133_061-shape-tools-square-and-icon-alignment
1 parent a00bc3e commit 7db3b3c

9 files changed

Lines changed: 259 additions & 44 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

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

3-
Task: PR_26133_059-palette-opacity-mode-and-application-flow
3+
Task: PR_26133_061-shape-tools-square-and-icon-alignment
44
Date: 2026-05-15
55

66
## Result
@@ -24,7 +24,8 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
2424
## Relevant Runtime Coverage
2525

2626
```text
27-
(95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 5669/5669; executed functions 600/631
27+
(94%) src/engine/rendering/ObjectVectorRuntimeAssetService.js - executed lines 1131/1131; executed functions 111/118
28+
(95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 5710/5710; executed functions 601/632
2829
```
2930

3031
## Guardrail
@@ -35,4 +36,4 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
3536

3637
## PR-Specific Note
3738

38-
The Workspace V2 run exercised Object Vector Studio V2 opacity control layout, active Fill/Stroke opacity selection without immediate shape mutation, shape-click opacity application through the existing Paint/Stroke workflow, schema validation, and Asteroids runtime object-vector rendering.
39+
The Workspace V2 run exercised Object Vector Studio V2 Square tool creation, schema validation for `tool: "square"` shapes backed by rectangle geometry, one-size Square geometry editing, Shape/Tools Nerd Font icon mapping and alignment, and Asteroids runtime object-vector rendering.
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_059 Workspace V2 Playwright Results
1+
# PR_26133_061 Workspace V2 Playwright Results
22

3-
Task: PR_26133_059-palette-opacity-mode-and-application-flow
3+
Task: PR_26133_061-shape-tools-square-and-icon-alignment
44
Date: 2026-05-15
55

66
## Result
@@ -9,19 +9,19 @@ PASS - `npm run test:workspace-v2` completed successfully.
99

1010
- Command: `npm run test:workspace-v2`
1111
- Playwright target: `tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list`
12-
- Final result: 52 passed, 0 failed.
12+
- Final result: 53 passed, 0 failed.
1313
- Runtime/console guard: Workspace Manager V2, Object Vector Studio V2, and Asteroids runtime scenarios completed with no reported page errors.
1414

1515
## PR-Specific Coverage
1616

17-
- Verified the Palette opacity controls render as `Opacity`, then compact `Fill` and `Stroke` inputs.
18-
- Verified changing Fill opacity updates the active Fill opacity value only and does not immediately mutate the selected shape.
19-
- Verified changing Stroke opacity updates the active Stroke opacity value only and does not immediately mutate the selected shape.
20-
- Verified clicking a shape applies the currently selected Fill/Stroke opacity values through the existing Paint/Stroke application flow.
21-
- Verified Paint/Stroke color mode behavior remains selection-first and shape-click apply.
17+
- Verified the new Square tool renders in Shape/Tools and creates a schema-valid `tool: "square"` shape backed by rectangle geometry.
18+
- Verified Square geometry uses one `Size` input and applies equal width/height values.
19+
- Verified Oval/Ellipse, Circle, Arc, and Square use the requested Nerd Font icon names.
20+
- Verified Shape/Tools icon spacing stays aligned with text labels visible and in icon-only mode.
21+
- Verified Object Vector Studio V2 and Asteroids runtime scenarios completed without page or console errors.
2222

2323
## Additional Validation
2424

25-
- Focused palette/layout slice passed:
26-
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "layout shell"` completed with 1 passed, 0 failed.
25+
- Focused Shape/Tools layout and Square creation slice passed:
26+
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "layout shell|square shapes"` completed with 2 passed, 0 failed.
2727
- `git diff --check` passed. The command reported existing Windows LF-to-CRLF warnings for touched files and no whitespace errors.

src/engine/rendering/ObjectVectorRuntimeAssetService.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,13 @@ function shapeTool(shape) {
106106

107107
function shapeGeometryTool(shape) {
108108
const tool = shapeTool(shape);
109-
return tool === "triangle" ? "polygon" : tool;
109+
if (tool === "triangle") {
110+
return "polygon";
111+
}
112+
if (tool === "square") {
113+
return "rectangle";
114+
}
115+
return tool;
110116
}
111117

112118
function shapeTransform(shape) {

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 145 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
13081308
await page.locator("#objectVectorStudioV2ObjectNameInput").fill("Blocked Object");
13091309
await page.locator("#objectVectorStudioV2AddObjectButton").click();
13101310
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Add object blocked: load a schema-valid Object Vector Studio V2 payload before adding objects\./);
1311-
await expect(page.locator(".object-vector-studio-v2__tool-toggle")).toHaveText(["Select", "Triangle", "Rectangle", "Circle", "Ellipse", "Line", "Polygon", "Arc", "Text"]);
1311+
await expect(page.locator(".object-vector-studio-v2__tool-toggle")).toHaveText(["Select", "Triangle", "Rectangle", "Square", "Circle", "Ellipse", "Line", "Polygon", "Arc", "Text"]);
13121312
await expect(page.locator(".object-vector-studio-v2__shape-icon--triangle")).toBeVisible();
13131313
await expect(page.locator(".object-vector-studio-v2__shape-icon--arc")).toBeVisible();
13141314
const iconStyleState = await page.evaluate(async () => {
@@ -1378,6 +1378,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
13781378
polygon: icon(".object-vector-studio-v2__shape-icon--polygon"),
13791379
rectangle: icon(".object-vector-studio-v2__shape-icon--rectangle"),
13801380
select: icon(".object-vector-studio-v2__shape-icon--select"),
1381+
square: icon(".object-vector-studio-v2__shape-icon--square"),
13811382
text: icon(".object-vector-studio-v2__shape-icon--text"),
13821383
triangle: icon(".object-vector-studio-v2__shape-icon--triangle")
13831384
},
@@ -1444,23 +1445,34 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14441445
polygon: "polygon",
14451446
rectangle: "rectangle",
14461447
select: "select",
1448+
square: "square",
14471449
text: "text",
14481450
triangle: "triangle"
14491451
});
1452+
expect(iconStyleState.shapeIcons.arc.iconName).toBe("nf-md-vector_radius");
1453+
expect(iconStyleState.shapeIcons.circle.iconName).toBe("nf-md-vector_circle_variant");
1454+
expect(iconStyleState.shapeIcons.ellipse.iconName).toBe("nf-md-vector_ellipse");
14501455
expect(iconStyleState.shapeIcons.polygon.iconName).toBe("nf-md-vector_polygon");
1456+
expect(iconStyleState.shapeIcons.square.iconName).toBe("nf-fa-vector_square");
14511457
expect(iconStyleState.shapeIcons.triangle.iconName).toBe("nf-md-vector_triangle");
14521458
expect(iconStyleState.shapeIcons.select.iconName).toBe("nf-md-select");
14531459
expect(iconStyleState.shapeIcons.line.iconName).toBe("nf-md-vector_line");
14541460
expect(iconStyleState.shapeIcons.rectangle.iconName).toBe("nf-md-vector_rectangle");
1455-
expect(iconStyleState.shapeIcons.circle.iconName).toBe("nf-fa-circle_o");
1456-
expect(iconStyleState.shapeIcons.ellipse.iconName).toBe("nf-fa-circle_o");
14571461
expect(Math.round(iconStyleState.shapeIcons.select.fontSize)).toBe(Math.round(iconStyleState.shapeIcons.circle.fontSize * 1.125));
14581462
expect(Math.round(iconStyleState.shapeIcons.triangle.fontSize)).toBe(Math.round(iconStyleState.shapeIcons.circle.fontSize * 1.25));
14591463
expect(Math.round(iconStyleState.shapeIcons.rectangle.fontSize)).toBe(Math.round(iconStyleState.shapeIcons.circle.fontSize * 1.25));
1460-
expect(iconStyleState.shapeIcons.select.transform).not.toBe("none");
1461-
expect(iconStyleState.shapeIcons.triangle.transform).not.toBe("none");
1462-
expect(iconStyleState.shapeIcons.line.transform).not.toBe("none");
1463-
expect(iconStyleState.shapeIcons.rectangle.transform).not.toBe("none");
1464+
expect(Object.fromEntries(Object.entries(iconStyleState.shapeIcons).map(([key, value]) => [key, value.transform]))).toEqual({
1465+
arc: "none",
1466+
circle: "none",
1467+
ellipse: "none",
1468+
line: "none",
1469+
polygon: "none",
1470+
rectangle: "none",
1471+
select: "none",
1472+
square: "none",
1473+
text: "none",
1474+
triangle: "none"
1475+
});
14641476
expect(Object.fromEntries(Object.entries(iconStyleState.viewportIcons).map(([key, value]) => [key, value.iconKey]))).toEqual({
14651477
down: "panDown",
14661478
reset: "reset",
@@ -1488,19 +1500,29 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14881500
const shapeToolsContent = document.querySelector("#objectVectorStudioV2ShapeToolsContent").getBoundingClientRect();
14891501
const shapeToolsAccordion = document.querySelector("#objectVectorStudioV2ShapeToolsContent").closest(".accordion-v2").getBoundingClientRect();
14901502
const leftPanel = document.querySelector(".tool-starter__panel--left");
1503+
const toolButtons = Array.from(document.querySelectorAll(".object-vector-studio-v2__tool-toggle"));
1504+
const iconTopOffsets = toolButtons.map((toolButton) => {
1505+
const buttonRect = toolButton.getBoundingClientRect();
1506+
const iconRect = toolButton.querySelector(".object-vector-studio-v2__shape-icon").getBoundingClientRect();
1507+
return Math.round(iconRect.top - buttonRect.top);
1508+
});
14911509
return {
1510+
buttonCount: toolButtons.length,
14921511
labelBesideGrid: gridButton.getBoundingClientRect().right <= labelButton.getBoundingClientRect().left,
14931512
leftPanelOverflowY: getComputedStyle(leftPanel).overflowY,
14941513
textButtonWider: Math.round(rect.width) > Math.round(rect.height),
1514+
visibleIconTopOffsetRange: Math.max(...iconTopOffsets) - Math.min(...iconTopOffsets),
14951515
shapeToolsReachesBottom: Math.abs(shapeToolsContent.bottom - shapeToolsAccordion.bottom) <= 1,
14961516
zOrderAbsentBeforeObjectSelection: !document.querySelector(".object-vector-studio-v2__z-order-actions"),
14971517
zOrderAbsentFromShapeTools: !document.querySelector("#objectVectorStudioV2ShapeToolsContent .object-vector-studio-v2__z-order-actions")
14981518
};
14991519
});
15001520
expect(shapeToolLayout).toEqual({
1521+
buttonCount: 10,
15011522
labelBesideGrid: true,
15021523
leftPanelOverflowY: "auto",
15031524
textButtonWider: true,
1525+
visibleIconTopOffsetRange: 0,
15041526
shapeToolsReachesBottom: true,
15051527
zOrderAbsentBeforeObjectSelection: true,
15061528
zOrderAbsentFromShapeTools: true
@@ -1511,6 +1533,24 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15111533
await expect(page.locator(".object-vector-studio-v2__z-order-actions")).toHaveCount(0);
15121534
await expect(page.locator("#objectVectorStudioV2ResetViewButton")).not.toHaveClass(/is-icon-only/);
15131535
await expect(page.locator("#statusLog")).toHaveValue(/OK Shape\/Tools display mode set to compact icons\./);
1536+
const iconOnlyToolLayout = await page.locator("#objectVectorStudioV2ToolToggleGrid").evaluate((grid) => {
1537+
const toolButtons = Array.from(grid.querySelectorAll(".object-vector-studio-v2__tool-toggle"));
1538+
const measurements = toolButtons.map((toolButton) => {
1539+
const buttonRect = toolButton.getBoundingClientRect();
1540+
const iconRect = toolButton.querySelector(".object-vector-studio-v2__shape-icon").getBoundingClientRect();
1541+
return {
1542+
centerDeltaX: Math.abs((iconRect.left + iconRect.width / 2) - (buttonRect.left + buttonRect.width / 2)),
1543+
centerDeltaY: Math.abs((iconRect.top + iconRect.height / 2) - (buttonRect.top + buttonRect.height / 2)),
1544+
height: Math.round(buttonRect.height),
1545+
width: Math.round(buttonRect.width)
1546+
};
1547+
});
1548+
return {
1549+
iconsCentered: measurements.every((entry) => entry.centerDeltaX < 1 && entry.centerDeltaY < 1),
1550+
squareButtons: measurements.every((entry) => entry.width === entry.height)
1551+
};
1552+
});
1553+
expect(iconOnlyToolLayout).toEqual({ iconsCentered: true, squareButtons: true });
15141554
await page.reload({ waitUntil: "networkidle" });
15151555
await expect(page.locator("#objectVectorStudioV2ToolToggleGrid")).toHaveClass(/is-icon-only/);
15161556
await expect(page.locator(".object-vector-studio-v2__z-order-actions")).toHaveCount(0);
@@ -3020,6 +3060,104 @@ test.describe("Workspace Manager V2 bootstrap", () => {
30203060
}
30213061
});
30223062

3063+
test("creates Object Vector Studio V2 square shapes with one size control", async ({ page }) => {
3064+
const server = await startRepoServer();
3065+
const pageErrors = [];
3066+
const consoleErrors = [];
3067+
3068+
page.on("pageerror", (error) => {
3069+
pageErrors.push(error.message);
3070+
});
3071+
page.on("console", (message) => {
3072+
if (message.type() === "error") {
3073+
consoleErrors.push(message.text());
3074+
}
3075+
});
3076+
3077+
await coverageReporter.start(page);
3078+
try {
3079+
await page.goto(`${server.baseUrl}/tools/object-vector-studio-v2/index.html`, { waitUntil: "networkidle" });
3080+
await page.evaluate(() => {
3081+
sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
3082+
id: "square-tool-palette",
3083+
swatches: [
3084+
{ id: "white", value: "#ffffff" }
3085+
]
3086+
}));
3087+
});
3088+
await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles({
3089+
buffer: Buffer.from(JSON.stringify({
3090+
name: "Square Tool Check",
3091+
objects: [
3092+
{
3093+
id: "object.square.tool-check",
3094+
name: "Square Tool Check",
3095+
shapes: [],
3096+
tags: []
3097+
}
3098+
],
3099+
toolId: "object-vector-studio-v2",
3100+
version: 1
3101+
}, null, 2)),
3102+
mimeType: "application/json",
3103+
name: "square-tool-check.object-vector.json"
3104+
});
3105+
3106+
await page.locator('[data-shape-tool="square"]').click();
3107+
await expect(page.locator("#statusLog")).toHaveValue(/OK Created square shape on Square Tool Check\./);
3108+
await expect(page.locator("#objectVectorStudioV2ObjectDetails")).toContainText("Square Geometry");
3109+
await expect(page.locator(".object-vector-studio-v2__shape-select-label")).toHaveText("0. Square");
3110+
const createdSquare = await page.evaluate(() => {
3111+
const app = window.__objectVectorStudioV2App;
3112+
const fields = Array.from(document.querySelectorAll("#objectVectorStudioV2ObjectDetails [data-shape-geometry-field]")).map((input) => ({
3113+
key: input.dataset.shapeGeometryField,
3114+
label: input.closest("label").querySelector("span").textContent.trim(),
3115+
value: input.value
3116+
}));
3117+
const shape = app.selectedShape();
3118+
return {
3119+
fields,
3120+
geometry: shape.geometry,
3121+
schemaOk: app.schemaService.validatePayload(app.currentPayload).ok,
3122+
tool: shape.tool
3123+
};
3124+
});
3125+
expect(createdSquare).toEqual({
3126+
fields: [
3127+
{ key: "x", label: "x", value: "-80" },
3128+
{ key: "y", label: "y", value: "-30" },
3129+
{ key: "size", label: "Size", value: "60" }
3130+
],
3131+
geometry: { height: 60, width: 60, x: -80, y: -30 },
3132+
schemaOk: true,
3133+
tool: "square"
3134+
});
3135+
3136+
await page.locator("#objectVectorStudioV2ObjectDetails [data-shape-geometry-field='size']").fill("42");
3137+
await page.locator("#objectVectorStudioV2ApplyGeometryButton").click();
3138+
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied geometry edits to shape row 0\./);
3139+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='0']")).toHaveAttribute("width", "420");
3140+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='0']")).toHaveAttribute("height", "420");
3141+
const resizedSquare = await page.evaluate(() => {
3142+
const app = window.__objectVectorStudioV2App;
3143+
return {
3144+
geometry: app.selectedShape().geometry,
3145+
schemaOk: app.schemaService.validatePayload(app.currentPayload).ok
3146+
};
3147+
});
3148+
expect(resizedSquare).toEqual({
3149+
geometry: { height: 42, width: 42, x: -80, y: -30 },
3150+
schemaOk: true
3151+
});
3152+
3153+
expect(pageErrors).toEqual([]);
3154+
expect(consoleErrors).toEqual([]);
3155+
} finally {
3156+
await coverageReporter.stop(page);
3157+
await server.close();
3158+
}
3159+
});
3160+
30233161
test("maps Object Vector Studio V2 preview coordinates directly to visible grid lines", async ({ page }) => {
30243162
const server = await startRepoServer();
30253163
const pageErrors = [];

tools/object-vector-studio-v2/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
190190
<span class="object-vector-studio-v2__shape-icon object-vector-studio-v2__shape-icon--rectangle" aria-hidden="true"></span>
191191
<span class="object-vector-studio-v2__tool-label">Rectangle</span>
192192
</button>
193+
<button class="object-vector-studio-v2__tool-toggle" type="button" aria-pressed="false" data-shape-tool="square" title="Create a square shape on the selected object">
194+
<span class="object-vector-studio-v2__shape-icon object-vector-studio-v2__shape-icon--square" aria-hidden="true"></span>
195+
<span class="object-vector-studio-v2__tool-label">Square</span>
196+
</button>
193197
<button class="object-vector-studio-v2__tool-toggle" type="button" aria-pressed="false" data-shape-tool="circle" title="Create a circle shape on the selected object">
194198
<span class="object-vector-studio-v2__shape-icon object-vector-studio-v2__shape-icon--circle" aria-hidden="true"></span>
195199
<span class="object-vector-studio-v2__tool-label">Circle</span>

0 commit comments

Comments
 (0)