Skip to content

Commit 571c0b2

Browse files
author
DavidQ
committed
Add schema driven New document workflow to Object Vector Studio V2 - PR_26133_039-object-vector-start-new-empty-state
1 parent d38eb10 commit 571c0b2

6 files changed

Lines changed: 547 additions & 66 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# PR_26133_038 Object Transform Scale Resize Geometry Report
2+
3+
Task: PR_26133_038-object-transform-scale-resize-geometry
4+
Date: 2026-05-15
5+
6+
## Implementation
7+
8+
- Replaced the old dead resize textbox flow with a live Scale row and explicit Resize Geometry action.
9+
- Scale controls now render in the requested order:
10+
`Scale [--] [-] [scale input] [+] [++] [Resize]`.
11+
- Added scale step buttons:
12+
- `--` decreases by 0.10.
13+
- `-` decreases by 0.01.
14+
- `+` increases by 0.01.
15+
- `++` increases by 0.10.
16+
- Scale input validates finite positive values, rejects invalid values visibly, and does not silently restore a previous value.
17+
- Valid scale input updates the selected shape preview live and keeps JSON details and transform summary in sync.
18+
- Resize Geometry applies the current scale to selected shape geometry, resets transform `scaleX` and `scaleY` to 1, validates the payload, marks workspace state dirty on persisted changes, and logs OK/FAIL status messages.
19+
20+
## Geometry Resize Support
21+
22+
- Polygon and triangle: scales each point around transform origin.
23+
- Line: scales `point1` and `point2` around transform origin.
24+
- Rectangle: scales x/y around transform origin and multiplies width/height.
25+
- Circle: scales center and radius, requiring uniform scale.
26+
- Ellipse: scales center and rx/ry.
27+
- Arc: scales center and radius, requiring uniform scale.
28+
- Text: scales x/y and font size, requiring uniform scale.
29+
30+
## Validation
31+
32+
- `npm run test:workspace-v2` passed with 51 tests.
33+
- Added Playwright coverage for supported shape resize baking and scale reset.
34+
- Existing Object Vector Studio V2 layout coverage now verifies the one-line Scale row and Resize Geometry tooltip.
35+
- Existing transform coverage now verifies invalid scale rejection, live preview updates, step buttons, geometry rewrite, scale reset, and transformed bounds refresh.
36+
- No Object Vector Studio V2 sample JSON or manifest data changes were kept.

docs/dev/reports/playwright_v8_coverage_report.md

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

3-
Task: PR_26133_037-object-preview-transform-bounds-fix
3+
Task: PR_26133_038-object-transform-scale-resize-geometry
44
Date: 2026-05-15
55

66
## Result
@@ -26,7 +26,7 @@ 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 4416/4416; executed functions 466/498
29+
(94%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 4646/4646; executed functions 479/510
3030
```
3131

3232
## Changed Test Coverage Note
@@ -43,4 +43,4 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
4343

4444
## PR-Specific Note
4545

46-
The Workspace V2 run exercised Object Vector Studio V2 transformed selection bounds, transformed handle placement, transformed hit testing, and drag interaction startup from the transformed visual shape region. Coverage remains advisory only.
46+
The Workspace V2 run exercised Object Vector Studio V2 live scale preview, scale step buttons, Resize Geometry baking, scale reset, schema validation, transformed selection bounds refresh, and supported shape-tool geometry rewrite paths. Coverage remains advisory only.
Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_037 Workspace V2 Playwright Results
1+
# PR_26133_038 Workspace V2 Playwright Results
22

3-
Task: PR_26133_037-object-preview-transform-bounds-fix
3+
Task: PR_26133_038-object-transform-scale-resize-geometry
44
Date: 2026-05-15
55

66
## Result
@@ -9,17 +9,20 @@ 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: 50 passed, 0 failed.
12+
- Final result: 51 passed, 0 failed.
1313
- Runtime/console guard: Object Vector Studio V2 tests that monitor page errors and console errors completed with no reported errors.
1414

1515
## PR-Specific Coverage
1616

17-
- Added `aligns Object Vector Studio V2 selection bounds to transformed preview geometry`.
18-
- Verified the dashed selection rectangle is calculated from transformed geometry corners, including x/y, rotation, scaleX/scaleY, and origin.
19-
- Verified all four resize handles align to the transformed selection bounds.
20-
- Verified the transformed shape center is outside the raw geometry bounds but still hit-tests against the selected SVG shape.
21-
- Verified drag interaction can start from the transformed visual region and updates the selected shape transform.
17+
- Verified Object Transform scale controls render on one line in the requested order:
18+
`Scale [--] [-] [scale input] [+] [++] [Resize]`.
19+
- Verified the Resize button keeps visible text `Resize` and tooltip/title `Resize Geometry`.
20+
- Verified invalid scale input is visibly rejected and does not write `scaleX: 0`.
21+
- Verified live scale input updates the Object Preview, JSON details, and transform summary.
22+
- Verified `--`, `-`, `+`, and `++` buttons step scale by 0.10, 0.01, 0.01, and 0.10 respectively.
23+
- Verified Resize Geometry bakes current scale into rectangle geometry, resets scale to 1, and updates transformed selection bounds.
24+
- Added coverage for Resize Geometry across polygon, triangle, line, rectangle, circle, ellipse, arc, and text shapes.
2225

2326
## Manual Verification Equivalent
2427

25-
Targeted Object Vector Studio V2 browser automation covered the requested scaled/rotated selection behavior, transformed hit testing, drag interaction continuity, and no-console-error checks.
28+
Targeted Object Vector Studio V2 browser automation covered the requested scale layout, live preview, geometry resize, transformed bounds refresh, invalid input rejection, and no-console-error checks.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 172 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,16 +1826,31 @@ test.describe("Workspace Manager V2 bootstrap", () => {
18261826
const transformIconState = await page.locator("#objectVectorStudioV2ObjectTransform").evaluate((panel) => ({
18271827
resize: {
18281828
iconKey: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconKey,
1829-
iconName: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconName
1829+
iconName: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconName,
1830+
title: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").title
18301831
},
1831-
scale: {
1832-
iconKey: panel.querySelector("#objectVectorStudioV2ScaleShapeButton").dataset.ovsIconKey,
1833-
iconName: panel.querySelector("#objectVectorStudioV2ScaleShapeButton").dataset.ovsIconName
1834-
}
1832+
scaleActionRemoved: panel.querySelector("#objectVectorStudioV2ScaleShapeButton") === null
18351833
}));
18361834
expect(transformIconState).toEqual({
1837-
resize: { iconKey: "resize", iconName: "nf-md-resize" },
1838-
scale: { iconKey: "scale", iconName: "nf-fa-scale_unbalanced" }
1835+
resize: { iconKey: "resize", iconName: "nf-md-resize", title: "Resize Geometry" },
1836+
scaleActionRemoved: true
1837+
});
1838+
const scaleControlLayout = await page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__scale-control-row").evaluate((row) => {
1839+
const children = Array.from(row.children);
1840+
const centers = children.map((child) => {
1841+
const rect = child.getBoundingClientRect();
1842+
return Math.round(rect.top + rect.height / 2);
1843+
});
1844+
return {
1845+
allOneLine: centers.every((center) => Math.abs(center - centers[0]) <= 2),
1846+
order: children.map((child) => child.tagName === "INPUT" ? child.value : child.textContent.trim()),
1847+
resizeTitle: row.querySelector("#objectVectorStudioV2ResizeShapeButton").title
1848+
};
1849+
});
1850+
expect(scaleControlLayout).toEqual({
1851+
allOneLine: true,
1852+
order: ["Scale", "--", "-", "1", "+", "++", "Resize"],
1853+
resizeTitle: "Resize Geometry"
18391854
});
18401855
const objectDetailsOrder = await page.locator("#objectVectorStudioV2ObjectDetails").evaluate((details) => {
18411856
const applyButton = details.querySelector("#objectVectorStudioV2ApplyGeometryButton");
@@ -1873,10 +1888,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
18731888
{ inline: true, label: "Move X" },
18741889
{ inline: true, label: "Move Y" },
18751890
{ inline: true, label: "Rotate" },
1876-
{ inline: true, label: "Scale" },
18771891
{ inline: true, label: "Origin X" },
1878-
{ inline: true, label: "Origin Y" },
1879-
{ inline: true, label: "Resize" }
1892+
{ inline: true, label: "Origin Y" }
18801893
]);
18811894
const transformBeforeInvalid = await page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().transform);
18821895
await page.locator("#objectVectorStudioV2MoveXInput").fill("");
@@ -2223,19 +2236,67 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22232236
await expect(page.locator("#statusLog")).toHaveValue(/OK Rotated shape row 0 by 15 degrees\./);
22242237

22252238
await page.locator("#objectVectorStudioV2ScaleInput").fill("0");
2226-
await page.locator("#objectVectorStudioV2ScaleShapeButton").click();
2239+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveAttribute("aria-invalid", "true");
22272240
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Invalid transform rejected for shape row 0: scale must be greater than 0\./);
22282241
await expect(page.locator("#objectVectorStudioV2JsonDetails")).not.toContainText('"scaleX": 0');
22292242
await page.locator("#objectVectorStudioV2ScaleInput").fill("1.2");
2230-
await page.locator("#objectVectorStudioV2ScaleShapeButton").click();
2243+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).not.toHaveAttribute("aria-invalid", "true");
2244+
await expect(page.locator("#statusLog")).toHaveValue(/OK Scale preview set to 1\.2 for shape row 0\./);
22312245
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"scaleX": 1.2');
2232-
await expect(page.locator("#statusLog")).toHaveValue(/OK Scaled shape row 0 by 1\.2\./);
2233-
2234-
await page.locator("#objectVectorStudioV2ResizeInput").fill("5");
2246+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("Transform x 10, y 10, rot 15, scale 1.2 x 1.2");
2247+
const selectionBeforeScaleStep = await page.locator("#objectVectorStudioV2RenderSurface [data-selection-bounds='0']").evaluate((box) => ({
2248+
height: Number(box.getAttribute("height")),
2249+
width: Number(box.getAttribute("width"))
2250+
}));
2251+
await page.locator("#objectVectorStudioV2ScaleDownSmallButton").click();
2252+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.19");
2253+
await page.locator("#objectVectorStudioV2ScaleDownLargeButton").click();
2254+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.09");
2255+
await page.locator("#objectVectorStudioV2ScaleUpSmallButton").click();
2256+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.1");
2257+
await page.locator("#objectVectorStudioV2ScaleUpLargeButton").click();
2258+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.2");
2259+
const selectionAfterScaleStep = await page.locator("#objectVectorStudioV2RenderSurface [data-selection-bounds='0']").evaluate((box) => ({
2260+
height: Number(box.getAttribute("height")),
2261+
width: Number(box.getAttribute("width"))
2262+
}));
2263+
expect(selectionAfterScaleStep.width).toBeCloseTo(selectionBeforeScaleStep.width, 1);
2264+
expect(selectionAfterScaleStep.height).toBeCloseTo(selectionBeforeScaleStep.height, 1);
2265+
const rectangleBeforeResizeGeometry = await page.evaluate(() => {
2266+
const shape = window.__objectVectorStudioV2App.selectedShape();
2267+
return {
2268+
geometry: { ...shape.geometry },
2269+
transform: { ...shape.transform, origin: { ...shape.transform.origin } }
2270+
};
2271+
});
22352272
await page.locator("#objectVectorStudioV2ResizeShapeButton").click();
2236-
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"width": 85');
2237-
await expect(page.locator("#objectVectorStudioV2ResizeInput")).toHaveValue("5");
2238-
await expect(page.locator("#statusLog")).toHaveValue(/OK Resized shape row 0 by 5\./);
2273+
const rectangleAfterResizeGeometry = await page.evaluate(() => {
2274+
const shape = window.__objectVectorStudioV2App.selectedShape();
2275+
return {
2276+
geometry: { ...shape.geometry },
2277+
schemaOk: window.__objectVectorStudioV2App.schemaService.validatePayload(window.__objectVectorStudioV2App.currentPayload).ok,
2278+
transform: { ...shape.transform, origin: { ...shape.transform.origin } }
2279+
};
2280+
});
2281+
expect(rectangleAfterResizeGeometry).toMatchObject({
2282+
geometry: {
2283+
height: Number((rectangleBeforeResizeGeometry.geometry.height * 1.2).toFixed(3)),
2284+
width: Number((rectangleBeforeResizeGeometry.geometry.width * 1.2).toFixed(3)),
2285+
x: Number((rectangleBeforeResizeGeometry.transform.origin.x + (rectangleBeforeResizeGeometry.geometry.x - rectangleBeforeResizeGeometry.transform.origin.x) * 1.2).toFixed(3)),
2286+
y: Number((rectangleBeforeResizeGeometry.transform.origin.y + (rectangleBeforeResizeGeometry.geometry.y - rectangleBeforeResizeGeometry.transform.origin.y) * 1.2).toFixed(3))
2287+
},
2288+
schemaOk: true,
2289+
transform: {
2290+
rotation: rectangleBeforeResizeGeometry.transform.rotation,
2291+
scaleX: 1,
2292+
scaleY: 1,
2293+
x: rectangleBeforeResizeGeometry.transform.x,
2294+
y: rectangleBeforeResizeGeometry.transform.y
2295+
}
2296+
});
2297+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1");
2298+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("Transform x 10, y 10, rot 15, scale 1 x 1");
2299+
await expect(page.locator("#statusLog")).toHaveValue(/OK Resize Geometry applied scale 1\.2 to shape row 0; transform scale reset to 1\./);
22392300

22402301
await page.locator("#objectVectorStudioV2BringForwardButton").click();
22412302
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"order": 2');
@@ -3391,6 +3452,99 @@ test.describe("Workspace Manager V2 bootstrap", () => {
33913452
}
33923453
});
33933454

3455+
test("applies Object Vector Studio V2 Resize Geometry across supported shape tools", async ({ page }) => {
3456+
const server = await startRepoServer();
3457+
const pageErrors = [];
3458+
const consoleErrors = [];
3459+
const scaledTransform = { origin: { x: 0, y: 0 }, rotation: 0, scaleX: 1.5, scaleY: 1.5, x: 0, y: 0 };
3460+
const style = { fill: "#ffffff", fillOpacity: 1, stroke: "#60a5fa", strokeOpacity: 1, strokeWidth: 1 };
3461+
3462+
page.on("pageerror", (error) => {
3463+
pageErrors.push(error.message);
3464+
});
3465+
page.on("console", (message) => {
3466+
if (message.type() === "error") {
3467+
consoleErrors.push(message.text());
3468+
}
3469+
});
3470+
3471+
await coverageReporter.start(page);
3472+
try {
3473+
await page.setViewportSize({ width: 1366, height: 1000 });
3474+
await page.goto(`${server.baseUrl}/tools/object-vector-studio-v2/index.html`, { waitUntil: "networkidle" });
3475+
await page.evaluate(() => {
3476+
sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
3477+
id: "resize-geometry-palette",
3478+
swatches: [
3479+
{ id: "white", value: "#ffffff" },
3480+
{ id: "blue", value: "#60a5fa" }
3481+
]
3482+
}));
3483+
});
3484+
await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles({
3485+
buffer: Buffer.from(JSON.stringify({
3486+
name: "Resize Geometry Object Set",
3487+
objects: [
3488+
{
3489+
id: "object.resize.geometry-probe",
3490+
name: "Resize Geometry Probe",
3491+
shapes: [
3492+
{ geometry: { points: [{ x: 0, y: -10 }, { x: 10, y: 0 }, { x: 0, y: 10 }, { x: -10, y: 0 }] }, locked: false, order: 1, style, tool: "polygon", transform: scaledTransform, visible: true },
3493+
{ geometry: { points: [{ x: 0, y: -8 }, { x: 8, y: 8 }, { x: -8, y: 8 }] }, locked: false, order: 2, style, tool: "triangle", transform: scaledTransform, visible: true },
3494+
{ geometry: { point1: { x: -10, y: -5 }, point2: { x: 10, y: 5 } }, locked: false, order: 3, style: { ...style, fill: "none" }, tool: "line", transform: scaledTransform, visible: true },
3495+
{ geometry: { height: 10, width: 20, x: -10, y: -5 }, locked: false, order: 4, style, tool: "rectangle", transform: scaledTransform, visible: true },
3496+
{ geometry: { cx: 5, cy: -5, r: 10 }, locked: false, order: 5, style, tool: "circle", transform: scaledTransform, visible: true },
3497+
{ geometry: { cx: 4, cy: -6, rx: 8, ry: 12 }, locked: false, order: 6, style, tool: "ellipse", transform: scaledTransform, visible: true },
3498+
{ geometry: { cx: 2, cy: -4, r: 9, startAngle: -90, endAngle: 90 }, locked: false, order: 7, style: { ...style, fill: "none" }, tool: "arc", transform: scaledTransform, visible: true },
3499+
{ geometry: { fontSize: 12, text: "Hi", x: 6, y: -6 }, locked: false, order: 8, style, tool: "text", transform: scaledTransform, visible: true }
3500+
],
3501+
tags: []
3502+
}
3503+
],
3504+
toolId: "object-vector-studio-v2",
3505+
version: 1
3506+
}, null, 2)),
3507+
mimeType: "application/json",
3508+
name: "object-vector-resize-geometry.json"
3509+
});
3510+
3511+
for (let shapeIndex = 0; shapeIndex < 8; shapeIndex += 1) {
3512+
await page.locator(`[data-object-tile-shape-index='${shapeIndex}']`).click();
3513+
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.5");
3514+
await page.locator("#objectVectorStudioV2ResizeShapeButton").click();
3515+
await expect(page.locator("#statusLog")).toHaveValue(new RegExp(`OK Resize Geometry applied scale 1\\.5 to shape row ${shapeIndex}; transform scale reset to 1\\.`));
3516+
}
3517+
3518+
const resizeResult = await page.evaluate(() => {
3519+
const payload = window.__objectVectorStudioV2App.currentPayload;
3520+
const shapes = payload.objects[0].shapes;
3521+
return {
3522+
geometries: shapes.map((shape) => shape.geometry),
3523+
schemaOk: window.__objectVectorStudioV2App.schemaService.validatePayload(payload).ok,
3524+
scales: shapes.map((shape) => ({ scaleX: shape.transform.scaleX, scaleY: shape.transform.scaleY }))
3525+
};
3526+
});
3527+
expect(resizeResult.schemaOk).toBe(true);
3528+
expect(resizeResult.scales.every((scale) => scale.scaleX === 1 && scale.scaleY === 1)).toBe(true);
3529+
expect(resizeResult.geometries).toEqual([
3530+
{ points: [{ x: 0, y: -15 }, { x: 15, y: 0 }, { x: 0, y: 15 }, { x: -15, y: 0 }] },
3531+
{ points: [{ x: 0, y: -12 }, { x: 12, y: 12 }, { x: -12, y: 12 }] },
3532+
{ point1: { x: -15, y: -7.5 }, point2: { x: 15, y: 7.5 } },
3533+
{ height: 15, width: 30, x: -15, y: -7.5 },
3534+
{ cx: 7.5, cy: -7.5, r: 15 },
3535+
{ cx: 6, cy: -9, rx: 12, ry: 18 },
3536+
{ cx: 3, cy: -6, r: 13.5, startAngle: -90, endAngle: 90 },
3537+
{ fontSize: 18, text: "Hi", x: 9, y: -9 }
3538+
]);
3539+
3540+
expect(pageErrors).toEqual([]);
3541+
expect(consoleErrors).toEqual([]);
3542+
} finally {
3543+
await coverageReporter.stop(page);
3544+
await server.close();
3545+
}
3546+
});
3547+
33943548
test("expands Object Vector Studio V2 asset authoring controls", async ({ page }, testInfo) => {
33953549
const server = await startRepoServer();
33963550
const pageErrors = [];

0 commit comments

Comments
 (0)