Skip to content

Commit 107ab83

Browse files
author
DavidQ
committed
Align grid off icon color and add auto center balance from geometry - PR_26133_087-grid-off-color-and-auto-center-balance
1 parent 69d8a7a commit 107ab83

7 files changed

Lines changed: 154 additions & 45 deletions

File tree

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
11
# Playwright V8 Coverage Report
22

3-
PR: PR_26133_086-snap-angle-drawing-and-coordinate-formatting
3+
PR: PR_26133_087-grid-off-color-and-auto-center-balance
44

5-
Source: docs/dev/reports/playwright_v8_coverage_report.txt generated by the latest npm run test:workspace-v2 run.
5+
Command: `npm run test:workspace-v2`
66

7-
## Summary
7+
Result: PASS
88

9-
- PASS: V8 coverage was collected during the green workspace-v2 Playwright run.
10-
- Thresholds: advisory only; no blocking threshold configured.
11-
- Dependencies: no new npm packages.
9+
Coverage source: Playwright/Chromium built-in V8 coverage from the final passing workspace-v2 run.
1210

13-
## Exercised Tool Entry Points
11+
Changed runtime JS coverage:
12+
- `tools/object-vector-studio-v2/js/bootstrap.js`: 83% entry coverage, 110/110 executed lines, 5/6 executed functions.
13+
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 95% entry coverage, 7498/7498 executed lines, 751/789 executed functions.
1414

15-
- (82%) Preview Generator V2 - exercised 19 runtime JS files
16-
- (74%) Asset Manager V2 - exercised 13 runtime JS files
17-
- (62%) Palette Manager V2 - exercised 12 runtime JS files
18-
- (0%) Tool Template V2 - not exercised by this Playwright run
19-
- (91%) Workspace Manager V2 - exercised 10 runtime JS files
20-
- (0%) Workspace Manager - not exercised by this Playwright run
21-
22-
## Changed Runtime JS Files Covered
23-
24-
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 7440/7440; executed functions 743/781
25-
26-
## Changed JS Files Considered
27-
28-
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - Object Vector Studio V2 runtime covered by browser V8 coverage while Snap None 3-decimal coordinate creation, Snap Angle drawing constraints, and Palette Picker placement were exercised.
29-
- (0%) tests/playwright/tools/WorkspaceManagerV2.spec.mjs - Playwright test file, not collected as browser runtime coverage.
15+
Notes:
16+
- The generated detailed text artifact remains at `docs/dev/reports/playwright_v8_coverage_report.txt`.
17+
- Coverage is advisory for this PR; no new thresholds were introduced.
18+
- Changed runtime JS files were exercised by the passing Playwright run.
Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
11
# Playwright Workspace V2 Results
22

3-
PR: PR_26133_086-snap-angle-drawing-and-coordinate-formatting
3+
PR: PR_26133_087-grid-off-color-and-auto-center-balance
44

5-
## Validation
5+
Command: `npm run test:workspace-v2`
66

7-
- PASS: npm run test:workspace-v2
8-
- Result: 54 passed
9-
- Runtime: 5.4m
10-
- Browser project: playwright
11-
- Workers: 1
7+
Result: PASS
128

13-
## Targeted Checks Covered
9+
Summary:
10+
- 54/54 Playwright tests passed.
11+
- Final run completed in 6.1 minutes.
12+
- Object Vector Studio V2 Grid-off state now asserts that only the Grid icon uses the disabled red Snap color while button text stays default.
13+
- Auto Center coverage verifies the selected shape pivot moves to the visible object geometry bounds center, geometry JSON is unchanged, rendered bounds do not move, and workspace dirty state is set.
14+
- Existing Object Vector Studio V2 console/page error assertions remained clean in the covered flows.
1415

15-
- Snap None shape creation stores geometry coordinates with no more than 3 decimal places.
16-
- Snap Angle enabled at 45 degrees constrains committed Line creation segments.
17-
- Snap Angle enabled at 45 degrees constrains committed Polyline creation segments from the prior point.
18-
- Snap Angle enabled at 45 degrees constrains committed Polygon creation segments from the prior point.
19-
- Snap Grid and Snap Point creation checks remain green.
20-
- Palette primary row order is Paint, Picker, Stroke, Width, with Picker kept icon-only.
21-
22-
## Console/Runtime Errors
23-
24-
- PASS: Object Vector Studio V2 Playwright coverage collected no page errors or console errors in the exercised flows.
16+
Manual/targeted verification notes:
17+
- Grid off icon color matches disabled Snap Angle icon color.
18+
- Auto Center uses visible object bounds, supports asymmetric geometry center balancing, and logs an OK status after successful pivot update.
19+
- Auto Center changes transform origin/pivot only; geometry points are not modified and visible bounds remain stable.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15751575
titles: {
15761576
add: title("#objectVectorStudioV2AddObjectButton"),
15771577
angle: title("#objectVectorStudioV2AngleSnapButton"),
1578+
autoCenter: title("#objectVectorStudioV2AutoCenterButton"),
15781579
grid: title("#objectVectorStudioV2GridRenderButton"),
15791580
polygon: title("[data-shape-tool='polygon']"),
15801581
polyline: title("[data-shape-tool='polyline']"),
@@ -1583,6 +1584,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15831584
zoomIn: title("#objectVectorStudioV2ZoomInButton")
15841585
},
15851586
viewportIcons: {
1587+
autoCenter: icon("#objectVectorStudioV2AutoCenterButton"),
15861588
down: icon("#objectVectorStudioV2PanDownButton"),
15871589
reset: icon("#objectVectorStudioV2ResetViewButton"),
15881590
up: icon("#objectVectorStudioV2PanUpButton"),
@@ -1692,6 +1694,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16921694
triangle: "none"
16931695
});
16941696
expect(Object.fromEntries(Object.entries(iconStyleState.viewportIcons).map(([key, value]) => [key, value.iconKey]))).toEqual({
1697+
autoCenter: "center",
16951698
down: "panDown",
16961699
reset: "reset",
16971700
up: "panUp",
@@ -1703,6 +1706,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17031706
expect(iconStyleState.titles).toEqual({
17041707
add: "Add a schema-valid object to the loaded payload",
17051708
angle: "Snap Angle switches Rotate to a constrained dropdown using the selected 15, 30, 45, or 90 degree step.",
1709+
autoCenter: "Disabled until a schema-valid object is selected.",
17061710
grid: "Show or hide the preview grid",
17071711
polygon: "Create a polygon shape on the selected object. Click to add points.\n\nDouble-click to finish.",
17081712
polyline: "Create a polyline shape on the selected object. Click to add points.\n\nDouble-click to finish.",
@@ -2133,7 +2137,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
21332137
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
21342138
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(1);
21352139
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveAttribute("r", "9");
2136-
await expect(page.locator("#objectVectorStudioV2ViewportControls button")).toHaveText(["Out", "In", "Up", "Down", "Left", "Right", "View", "Center"]);
2140+
await expect(page.locator("#objectVectorStudioV2ViewportControls button")).toHaveText(["Out", "In", "Up", "Down", "Left", "Right", "View", "Center", "Auto Center"]);
21372141
await expect(page.locator("#objectVectorStudioV2CenterDotButton")).toHaveAttribute("aria-pressed", "true");
21382142
await page.locator("#objectVectorStudioV2PanRightButton").click();
21392143
await page.locator("#objectVectorStudioV2PanDownButton").click();
@@ -5399,6 +5403,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
53995403
await expect(page.locator("#objectVectorStudioV2GridRenderButton")).toHaveAttribute("aria-pressed", "false");
54005404
await expect(page.locator("#objectVectorStudioV2RenderSurface")).not.toHaveClass(/is-grid-visible/);
54015405
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-grid-rendered='true']")).toHaveCount(0);
5406+
const gridOffIconColorState = await page.evaluate(() => ({
5407+
gridIcon: getComputedStyle(document.querySelector("#objectVectorStudioV2GridRenderButton"), "::before").color,
5408+
gridText: getComputedStyle(document.querySelector("#objectVectorStudioV2GridRenderButton")).color,
5409+
snapAngleDisabledIcon: getComputedStyle(document.querySelector("#objectVectorStudioV2AngleSnapButton"), "::before").color
5410+
}));
5411+
expect(gridOffIconColorState.gridIcon).toBe(gridOffIconColorState.snapAngleDisabledIcon);
5412+
expect(gridOffIconColorState.gridText).not.toBe(gridOffIconColorState.snapAngleDisabledIcon);
54025413

54035414
await page.evaluate(() => window.__objectVectorStudioV2App.selectShape(0, "test shape list", { additive: true }));
54045415
await page.locator(".object-vector-studio-v2__object-tile.is-selected .object-vector-studio-v2__shape-list-actions [data-shape-list-action='group']").click();
@@ -9966,6 +9977,56 @@ test.describe("Workspace Manager V2 bootstrap", () => {
99669977
await page.locator("#objectVectorStudioV2MoveYInput").fill("-5");
99679978
await page.locator("#objectVectorStudioV2MoveShapeButton").click();
99689979
});
9980+
await expectObjectVectorDirtyAfter("object auto center edit", async () => {
9981+
await page.evaluate(() => {
9982+
const app = window.__objectVectorStudioV2App;
9983+
app.selectObject("object.asteroids.ship", "auto center dirty test");
9984+
app.selectShape(0, "auto center dirty test");
9985+
const shape = app.selectedShape();
9986+
const frame = app.activeFrame();
9987+
const override = frame?.shapeOverrides?.find((entry) => entry.shapeIndex === app.selectedShapeIndex) || null;
9988+
const target = override || shape;
9989+
target.transform = app.ensureShapeTransform(app.effectiveShape(shape));
9990+
target.transform.origin = { x: 123, y: -45 };
9991+
app.renderPayload({ syncPaletteSelection: false });
9992+
});
9993+
const autoCenterBefore = await page.evaluate(() => {
9994+
const app = window.__objectVectorStudioV2App;
9995+
const shape = app.selectedShape();
9996+
const effectiveShape = app.effectiveShape(shape);
9997+
const transform = app.shapeTransform(effectiveShape);
9998+
const bounds = app.objectBounds(app.selectedObject(), { includeInvisible: false });
9999+
return {
10000+
bounds,
10001+
center: {
10002+
x: Number((bounds.x + bounds.width / 2).toFixed(3)),
10003+
y: Number((bounds.y + bounds.height / 2).toFixed(3))
10004+
},
10005+
geometryText: JSON.stringify(shape.geometry),
10006+
pivot: app.transformedPoint(transform.origin, transform)
10007+
};
10008+
});
10009+
await page.locator("#objectVectorStudioV2AutoCenterButton").click();
10010+
await expect(page.locator("#statusLog")).toHaveValue(/OK Auto Center balanced shape row \d+ origin\/pivot to visible object center/);
10011+
const autoCenterAfter = await page.evaluate(() => {
10012+
const app = window.__objectVectorStudioV2App;
10013+
const shape = app.selectedShape();
10014+
const effectiveShape = app.effectiveShape(shape);
10015+
const transform = app.shapeTransform(effectiveShape);
10016+
return {
10017+
bounds: app.objectBounds(app.selectedObject(), { includeInvisible: false }),
10018+
geometryText: JSON.stringify(shape.geometry),
10019+
pivot: app.transformedPoint(transform.origin, transform)
10020+
};
10021+
});
10022+
expect(autoCenterAfter.geometryText).toBe(autoCenterBefore.geometryText);
10023+
["x", "y", "width", "height"].forEach((key) => {
10024+
expect(autoCenterAfter.bounds[key]).toBeCloseTo(autoCenterBefore.bounds[key], 3);
10025+
});
10026+
expect(autoCenterAfter.pivot.x).toBeCloseTo(autoCenterBefore.center.x, 3);
10027+
expect(autoCenterAfter.pivot.y).toBeCloseTo(autoCenterBefore.center.y, 3);
10028+
expect(autoCenterAfter.pivot.x).not.toBeCloseTo(autoCenterBefore.pivot.x, 3);
10029+
});
996910030
await expectObjectVectorDirtyAfter("palette color edit", async () => {
997010031
await page.locator("#objectVectorStudioV2PaintModeButton").click();
997110032
const colorToApply = await page.evaluate(() => {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
121121
<button id="objectVectorStudioV2PanRightButton" type="button" title="Pan the work surface right">Right</button>
122122
<button id="objectVectorStudioV2ResetViewButton" type="button" title="Reset zoom and origin to 0,0">View</button>
123123
<button id="objectVectorStudioV2CenterDotButton" type="button" title="Show or hide the center dot">Center</button>
124+
<button id="objectVectorStudioV2AutoCenterButton" type="button" title="Balance selected shape origin/pivot to the visible object center">Auto Center</button>
124125
</div>
125126
<svg id="objectVectorStudioV2RenderSurface" class="object-vector-studio-v2__render-surface" viewBox="0 0 320 220" tabindex="0" role="img" aria-label="Object shape render surface"></svg>
126127
<div id="objectVectorStudioV2ObjectPreviewFooter" class="object-vector-studio-v2__preview-footer">Object ID: none</div>

tools/object-vector-studio-v2/js/ToolStarterApp.js

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ const OBJECT_VECTOR_STUDIO_STATIC_ICON_TARGETS = Object.freeze([
118118
["#objectVectorStudioV2PanRightButton", "panRight"],
119119
["#objectVectorStudioV2ResetViewButton", "reset"],
120120
["#objectVectorStudioV2CenterDotButton", "center"],
121+
["#objectVectorStudioV2AutoCenterButton", "center"],
121122
["#objectVectorStudioV2FrameLeftButton", "panLeft"],
122123
["#objectVectorStudioV2FrameEarlierButton", "panLeft"],
123124
["#objectVectorStudioV2DuplicateFrameButton", "duplicate"],
@@ -754,6 +755,7 @@ export class ToolStarterApp {
754755
this.statusLog.write(`OK Grid rendering ${this.gridRenderEnabled ? "enabled" : "disabled"}.`);
755756
});
756757
this.elements.centerDotButton.addEventListener("click", () => this.toggleCenterOriginMarker());
758+
this.elements.autoCenterButton.addEventListener("click", () => this.autoCenterSelectedShapePivot());
757759
}
758760

759761
bindPaletteControls() {
@@ -3786,6 +3788,31 @@ export class ToolStarterApp {
37863788
};
37873789
}
37883790

3791+
localPointFromTransformedPoint(point, transform) {
3792+
const radians = (transform.rotation * Math.PI) / 180;
3793+
const cos = Math.cos(radians);
3794+
const sin = Math.sin(radians);
3795+
const dx = Number(point.x) - transform.x - transform.origin.x;
3796+
const dy = Number(point.y) - transform.y - transform.origin.y;
3797+
const unrotatedX = dx * cos + dy * sin;
3798+
const unrotatedY = -dx * sin + dy * cos;
3799+
return {
3800+
x: this.formatViewportNumber(transform.origin.x + unrotatedX / transform.scaleX),
3801+
y: this.formatViewportNumber(transform.origin.y + unrotatedY / transform.scaleY)
3802+
};
3803+
}
3804+
3805+
transformWithBalancedOrigin(transform, worldCenter) {
3806+
const normalized = this.ensureShapeTransform({ transform });
3807+
const origin = this.localPointFromTransformedPoint(worldCenter, normalized);
3808+
return {
3809+
...normalized,
3810+
origin,
3811+
x: this.formatViewportNumber(worldCenter.x - origin.x),
3812+
y: this.formatViewportNumber(worldCenter.y - origin.y)
3813+
};
3814+
}
3815+
37893816
transformedBounds(shape, { drawingScale = 1 } = {}) {
37903817
const transform = this.shapeTransform(shape);
37913818
const points = shapeBoundsPoints(shape).map((point) => this.transformedPoint(point, transform));
@@ -3806,9 +3833,14 @@ export class ToolStarterApp {
38063833
}
38073834

38083835
objectBounds(object, { drawingScale = 1, includeInvisible = true } = {}) {
3836+
const activeFrame = object?.id === this.selectedObjectId ? this.activeFrame() : null;
38093837
const shapes = sortedShapes(object)
38103838
.map((shape, shapeIndex) => ({ shape, shapeIndex }))
3811-
.filter((entry) => includeInvisible || entry.shape.visible);
3839+
.map(({ shape, shapeIndex }) => ({
3840+
shape: this.effectiveShapeForFrame(shape, activeFrame, shapeIndex),
3841+
shapeIndex
3842+
}))
3843+
.filter((entry) => includeInvisible || entry.shape.visible !== false);
38123844
if (!shapes.length) {
38133845
return {
38143846
height: 80,
@@ -3818,8 +3850,7 @@ export class ToolStarterApp {
38183850
};
38193851
}
38203852

3821-
const activeFrame = object?.id === this.selectedObjectId ? this.activeFrame() : null;
3822-
const bounds = shapes.map(({ shape, shapeIndex }) => this.transformedBounds(this.effectiveShapeForFrame(shape, activeFrame, shapeIndex), { drawingScale }));
3853+
const bounds = shapes.map(({ shape }) => this.transformedBounds(shape, { drawingScale }));
38233854
const minX = Math.min(...bounds.map((entry) => entry.x));
38243855
const minY = Math.min(...bounds.map((entry) => entry.y));
38253856
const maxX = Math.max(...bounds.map((entry) => entry.x + entry.width));
@@ -6503,6 +6534,35 @@ export class ToolStarterApp {
65036534
}, `OK Updated shape row ${this.selectedShapeIndex} origin/pivot to ${originX.value}, ${originY.value}.`);
65046535
}
65056536

6537+
autoCenterSelectedShapePivot() {
6538+
const object = this.selectedObject();
6539+
if (!object) {
6540+
this.statusLog.write("WARN Auto Center skipped: no object is selected.");
6541+
return;
6542+
}
6543+
const selected = this.selectedShape();
6544+
if (!selected) {
6545+
this.statusLog.write("WARN Auto Center skipped: no shape is selected.");
6546+
return;
6547+
}
6548+
const activeFrame = this.activeFrame();
6549+
const visibleShapes = sortedShapes(object)
6550+
.map((shape, shapeIndex) => this.effectiveShapeForFrame(shape, activeFrame, shapeIndex))
6551+
.filter((shape) => shape.visible !== false);
6552+
if (!visibleShapes.length) {
6553+
this.statusLog.write(`FAIL Auto Center blocked: object ${object.name} has no visible geometry.`);
6554+
return;
6555+
}
6556+
const bounds = this.objectBounds(object, { includeInvisible: false });
6557+
const center = {
6558+
x: this.formatViewportNumber(bounds.x + bounds.width / 2),
6559+
y: this.formatViewportNumber(bounds.y + bounds.height / 2)
6560+
};
6561+
this.updateSelectedShapeTransform("auto center", (shape) => {
6562+
shape.transform = this.transformWithBalancedOrigin(this.ensureShapeTransform(shape), center);
6563+
}, `OK Auto Center balanced shape row ${this.selectedShapeIndex} origin/pivot to visible object center ${center.x}, ${center.y}.`);
6564+
}
6565+
65066566
groupSelectedShapes() {
65076567
const object = this.selectedObject();
65086568
if (!object || this.selectedShapeIndexes.size < 2) {
@@ -7752,6 +7812,7 @@ export class ToolStarterApp {
77527812
this.setControlDisabled(this.elements.previewRedoButton, !this.previewRedoStack.length, "Disabled until an Object Preview edit can be redone.", "Redo the last undone Object Preview edit.");
77537813
this.setControlDisabled(this.elements.previewCopyButton, !shape, noShapeReason, "Copy the selected shape.");
77547814
this.setControlDisabled(this.elements.previewPasteButton, !object || !this.previewClipboardShape || isLocked, !object ? noObjectReason : (isLocked ? lockedReason : "Disabled until a shape has been copied."), "Paste the copied shape into the selected object.");
7815+
this.setControlDisabled(this.elements.autoCenterButton, !object || !shape || isLocked, !object ? noObjectReason : (isLocked ? lockedReason : noShapeReason), "Balance selected shape origin/pivot to the visible object center.");
77557816
}
77567817

77577818
updateObjectActionState() {

tools/object-vector-studio-v2/js/bootstrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ window.addEventListener("DOMContentLoaded", () => {
3838
addObjectButton: requireElement("#objectVectorStudioV2AddObjectButton"),
3939
addTagButton: requireElement("#objectVectorStudioV2AddTagButton"),
4040
angleSnapButton: requireElement("#objectVectorStudioV2AngleSnapButton"),
41+
autoCenterButton: requireElement("#objectVectorStudioV2AutoCenterButton"),
4142
centerDotButton: requireElement("#objectVectorStudioV2CenterDotButton"),
4243
coordinateDisplay: requireElement("#objectVectorStudioV2CoordinateDisplay"),
4344
deleteFrameButton: requireElement("#objectVectorStudioV2DeleteFrameButton"),

tools/object-vector-studio-v2/styles/toolStarter.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,8 @@ textarea:hover {
10261026
}
10271027

10281028
#objectVectorStudioV2SnapModeButton[data-snap-mode="none"][data-ovs-icon]::before,
1029-
#objectVectorStudioV2AngleSnapButton[aria-pressed="false"][data-ovs-icon]::before {
1029+
#objectVectorStudioV2AngleSnapButton[aria-pressed="false"][data-ovs-icon]::before,
1030+
#objectVectorStudioV2GridRenderButton[aria-pressed="false"][data-ovs-icon]::before {
10301031
color: var(--object-vector-studio-v2-snap-disabled-icon-color);
10311032
}
10321033

0 commit comments

Comments
 (0)