Skip to content

Commit 4ee1038

Browse files
author
DavidQ
committed
Correct Object Preview canvas fitting and enforce 1 to 1 coordinate grid scaling - PR_26133_003-object-preview-grid-scaling-and-canvas-fit
1 parent b45a1e3 commit 4ee1038

5 files changed

Lines changed: 168 additions & 23 deletions

File tree

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

3-
Coverage source: `docs/dev/reports/playwright_v8_coverage_report.txt`, refreshed by `npm run test:workspace-v2`.
3+
Coverage source: `docs/dev/reports/playwright_v8_coverage_report.txt`, refreshed by the final `npm run test:workspace-v2` run.
44

55
## Summary
66

77
- Coverage is advisory only; no thresholds are enforced.
88
- Workspace Manager V2 entry point: 91%.
9-
- Object Vector Studio V2 changed runtime files covered:
10-
- `tools/object-vector-studio-v2/js/bootstrap.js`: 80%, executed lines 99/99, executed functions 4/5.
9+
- Object Vector Studio V2 runtime coverage entries from the generated report:
1110
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 90%, executed lines 3270/3270, executed functions 342/378.
11+
- `tools/object-vector-studio-v2/js/bootstrap.js`: 80%, executed lines 99/99, executed functions 4/5.
1212
- The generated report lists no low-coverage changed runtime JS files.
1313

1414
## Validation Context
1515

1616
- Main command: `npm run test:workspace-v2`.
17-
- Result: 45 passed.
18-
- Focused Object Vector Studio V2 layout/preview/palette scenario also passed.
17+
- Result: 46 passed.
18+
- Focused Object Vector Studio V2 layout and preview-coordinate scenarios also passed.
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
# PR_26133_002 Workspace V2 Results
1+
# PR_26133_003 Workspace V2 Results
22

33
## Command Results
44

55
- `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`: passed.
66
- `node --check tools/object-vector-studio-v2/js/bootstrap.js`: passed.
77
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`: passed.
8-
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "shows Object Vector Studio V2 layout shell"`: 1 passed.
9-
- `npm run test:workspace-v2`: 45 passed.
8+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "Object Vector Studio V2 (layout shell|preview coordinates)"`: 2 passed.
9+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "blocks Workspace Manager V2 Save when the toolState file fails schema validation"`: 1 passed.
10+
- Final `npm run test:workspace-v2`: 46 passed.
1011
- `git diff --check`: passed.
1112

12-
## Targeted Object Vector Studio V2 Verification
13+
## Targeted Object Preview Verification
1314

1415
- Standalone Playwright manual pass reported `consoleErrors: []` and `pageErrors: []`.
15-
- Selected shape palette sync: rectangle restored cyan `#6fd3ff`; circle restored white `#ffffff`.
16-
- Centering dot button hid and restored the `data-center-origin="0,0"` marker.
17-
- Grid/object scale matched 1:1: rectangle `x=-80`, `y=-30`, `width=80`, `height=60`; grid included adjacent 10-unit lines and rectangle boundaries.
18-
- Grid snap moved a `13,7` edit to `10,10`.
19-
- Selection chrome measured 3x3 handles and `0.75px` dotted selection stroke.
20-
- Group action became enabled for multi-selection and assigned `group-1`.
21-
- Compact preview and palette accordions used `flex-grow: 0` and ended at content height; right rail remained scrollable.
16+
- Object Preview canvas filled the available work-area width at 1280px and after resizing to 1040px.
17+
- Render surface aspect ratio stayed aligned with the SVG viewBox: measured `1.4545` before and after resize.
18+
- Visible grid lines now map to coordinate units 1:1.
19+
- Asteroids ship points `0,-18`, `14,16`, `0,8`, `-14,16` aligned directly on visible grid lines.
20+
- Asteroids ship vertical span measured 18 visible grid lines above origin and 16 below origin.
21+
- Existing zoom viewBox behavior remained covered by the Object Vector Studio V2 layout scenario.
2222

2323
## Contract Checks
2424

25-
- Object Vector Studio V2 JSON remains palette-free.
2625
- No sample JSON files were modified.
27-
- No fallback behavior was added for palette sync or validation paths.
26+
- No unrelated tool/runtime files were changed.
27+
- No fallback behavior was added.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15781578
expect(shapeHierarchyDensity.rowHeight).toBeLessThanOrEqual(28);
15791579
const gridObjectScale = await page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface) => {
15801580
const rectangle = surface.querySelector("[data-shape-id='rectangle-1']");
1581+
const workArea = document.querySelector(".object-vector-studio-v2__work-area");
1582+
const workAreaStyle = getComputedStyle(workArea);
1583+
const availableWidth = workArea.clientWidth - Number.parseFloat(workAreaStyle.paddingLeft) - Number.parseFloat(workAreaStyle.paddingRight);
15811584
const verticalLines = Array.from(surface.querySelectorAll("[data-grid-rendered='true'] line"))
15821585
.filter((line) => line.getAttribute("x1") === line.getAttribute("x2"))
15831586
.map((line) => Number(line.getAttribute("x1")));
@@ -1588,21 +1591,23 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15881591
const surfaceRect = surface.getBoundingClientRect();
15891592
return {
15901593
aspectRatioMatchesViewBox: Math.abs((surfaceRect.width / surfaceRect.height) - (viewBox[2] / viewBox[3])) < 0.02,
1594+
canvasFillsAvailableWidth: Math.abs(surfaceRect.width - availableWidth) <= 2,
15911595
rectangle: {
15921596
height: Number(rectangle.getAttribute("height")),
15931597
width: Number(rectangle.getAttribute("width")),
15941598
x: Number(rectangle.getAttribute("x")),
15951599
y: Number(rectangle.getAttribute("y"))
15961600
},
15971601
rectangleSpansGridLines: verticalLines.includes(-80) && verticalLines.includes(0) && horizontalLines.includes(-30) && horizontalLines.includes(30),
1598-
stepMatchesSnap: verticalLines.includes(-80) && verticalLines.includes(-70)
1602+
unitGridSpacing: verticalLines.includes(-80) && verticalLines.includes(-79) && horizontalLines.includes(-30) && horizontalLines.includes(-29)
15991603
};
16001604
});
16011605
expect(gridObjectScale).toEqual({
16021606
aspectRatioMatchesViewBox: true,
1607+
canvasFillsAvailableWidth: true,
16031608
rectangle: { height: 60, width: 80, x: -80, y: -30 },
16041609
rectangleSpansGridLines: true,
1605-
stepMatchesSnap: true
1610+
unitGridSpacing: true
16061611
});
16071612
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='visibility']")).toHaveText("");
16081613
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='lock']")).toHaveText("");
@@ -2043,6 +2048,143 @@ test.describe("Workspace Manager V2 bootstrap", () => {
20432048
}
20442049
});
20452050

2051+
test("maps Object Vector Studio V2 preview coordinates directly to visible grid lines", async ({ page }) => {
2052+
const server = await startRepoServer();
2053+
const pageErrors = [];
2054+
const consoleErrors = [];
2055+
2056+
page.on("pageerror", (error) => {
2057+
pageErrors.push(error.message);
2058+
});
2059+
page.on("console", (message) => {
2060+
if (message.type() === "error") {
2061+
consoleErrors.push(message.text());
2062+
}
2063+
});
2064+
2065+
await coverageReporter.start(page);
2066+
try {
2067+
await page.goto(`${server.baseUrl}/tools/object-vector-studio-v2/index.html`, { waitUntil: "networkidle" });
2068+
await page.evaluate(() => {
2069+
sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
2070+
id: "asteroids-ship-grid",
2071+
swatches: [
2072+
{ id: "white", value: "#ffffff" },
2073+
{ id: "cyan", value: "#6fd3ff" }
2074+
]
2075+
}));
2076+
});
2077+
await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles({
2078+
buffer: Buffer.from(JSON.stringify({
2079+
name: "Asteroids Ship Grid Check",
2080+
objects: [
2081+
{
2082+
id: "object.asteroids.ship-grid",
2083+
name: "Asteroids Ship Grid Check",
2084+
shapes: [
2085+
{
2086+
geometry: {
2087+
points: [
2088+
{ x: 0, y: -18 },
2089+
{ x: 14, y: 16 },
2090+
{ x: 0, y: 8 },
2091+
{ x: -14, y: 16 }
2092+
]
2093+
},
2094+
id: "asteroids-ship-outline",
2095+
locked: false,
2096+
order: 1,
2097+
style: { fill: "#ffffff", stroke: "#6fd3ff", strokeWidth: 1 },
2098+
transform: { originX: 0, originY: 0, rotation: 0, scaleX: 1, scaleY: 1, x: 0, y: 0 },
2099+
type: "polygon",
2100+
visible: true
2101+
}
2102+
]
2103+
}
2104+
],
2105+
toolId: "object-vector-studio-v2",
2106+
version: 1
2107+
}, null, 2)),
2108+
mimeType: "application/json",
2109+
name: "asteroids-ship-grid.object-vector.json"
2110+
});
2111+
await expect(page.locator("#statusLog")).toHaveValue(/OK Render mode svg-work-surface: rendered Asteroids Ship Grid Check with 1 visible shapes; capture mode none\./);
2112+
2113+
const readPreviewScale = async () => page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface) => {
2114+
const workArea = document.querySelector(".object-vector-studio-v2__work-area");
2115+
const workAreaStyle = getComputedStyle(workArea);
2116+
const availableWidth = workArea.clientWidth - Number.parseFloat(workAreaStyle.paddingLeft) - Number.parseFloat(workAreaStyle.paddingRight);
2117+
const surfaceRect = surface.getBoundingClientRect();
2118+
const viewBox = surface.getAttribute("viewBox").split(/\s+/).map(Number);
2119+
const horizontalLines = Array.from(surface.querySelectorAll("[data-grid-rendered='true'] line"))
2120+
.filter((line) => line.getAttribute("y1") === line.getAttribute("y2"))
2121+
.map((line) => Number(line.getAttribute("y1")));
2122+
const verticalLines = Array.from(surface.querySelectorAll("[data-grid-rendered='true'] line"))
2123+
.filter((line) => line.getAttribute("x1") === line.getAttribute("x2"))
2124+
.map((line) => Number(line.getAttribute("x1")));
2125+
const screenPoint = (x, y) => {
2126+
const point = surface.createSVGPoint();
2127+
point.x = x;
2128+
point.y = y;
2129+
const transformed = point.matrixTransform(surface.getScreenCTM());
2130+
return { x: transformed.x, y: transformed.y };
2131+
};
2132+
const shipPoints = [
2133+
{ x: 0, y: -18 },
2134+
{ x: 14, y: 16 },
2135+
{ x: 0, y: 8 },
2136+
{ x: -14, y: 16 }
2137+
];
2138+
const pointScreens = shipPoints.map((point) => screenPoint(point.x, point.y));
2139+
const lineScreens = {
2140+
bottom: screenPoint(0, 16).y,
2141+
origin: screenPoint(0, 0).y,
2142+
top: screenPoint(0, -18).y
2143+
};
2144+
return {
2145+
aspectRatioStable: Math.abs((surfaceRect.width / surfaceRect.height) - (viewBox[2] / viewBox[3])) < 0.02,
2146+
canvasFillsAvailableWidth: Math.abs(surfaceRect.width - availableWidth) <= 2,
2147+
gridLinesAboveOrigin: horizontalLines.filter((y) => y < 0 && y >= -18).length,
2148+
gridLinesBelowOrigin: horizontalLines.filter((y) => y > 0 && y <= 16).length,
2149+
originCentered: Math.abs(lineScreens.origin - (surfaceRect.top + surfaceRect.height / 2)) <= 1,
2150+
pointsOnVisibleGridLines: shipPoints.every((point) => horizontalLines.includes(point.y) && verticalLines.includes(point.x)),
2151+
pointScreensMatchGrid: Math.abs(pointScreens[0].y - lineScreens.top) <= 0.01
2152+
&& Math.abs(pointScreens[1].y - lineScreens.bottom) <= 0.01
2153+
&& Math.abs(pointScreens[3].y - lineScreens.bottom) <= 0.01,
2154+
unitGridSpacing: horizontalLines.includes(-18) && horizontalLines.includes(-17) && horizontalLines.includes(15) && horizontalLines.includes(16),
2155+
viewBox: surface.getAttribute("viewBox")
2156+
};
2157+
});
2158+
2159+
const initialPreviewScale = await readPreviewScale();
2160+
expect(initialPreviewScale).toEqual({
2161+
aspectRatioStable: true,
2162+
canvasFillsAvailableWidth: true,
2163+
gridLinesAboveOrigin: 18,
2164+
gridLinesBelowOrigin: 16,
2165+
originCentered: true,
2166+
pointsOnVisibleGridLines: true,
2167+
pointScreensMatchGrid: true,
2168+
unitGridSpacing: true,
2169+
viewBox: "-160 -110 320 220"
2170+
});
2171+
2172+
await page.setViewportSize({ width: 1040, height: 720 });
2173+
const resizedPreviewScale = await readPreviewScale();
2174+
expect(resizedPreviewScale.aspectRatioStable).toBe(true);
2175+
expect(resizedPreviewScale.canvasFillsAvailableWidth).toBe(true);
2176+
expect(resizedPreviewScale.gridLinesAboveOrigin).toBe(18);
2177+
expect(resizedPreviewScale.gridLinesBelowOrigin).toBe(16);
2178+
expect(resizedPreviewScale.pointsOnVisibleGridLines).toBe(true);
2179+
2180+
expect(pageErrors).toEqual([]);
2181+
expect(consoleErrors).toEqual([]);
2182+
} finally {
2183+
await coverageReporter.stop(page);
2184+
await server.close();
2185+
}
2186+
});
2187+
20462188
test("expands Object Vector Studio V2 asset authoring controls", async ({ page }, testInfo) => {
20472189
const server = await startRepoServer();
20482190
const pageErrors = [];

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const GRID_RENDER_SESSION_KEY = "object-vector-studio-v2.gridRender";
1010
const CENTER_ORIGIN_SESSION_KEY = "object-vector-studio-v2.centerOrigin";
1111
const SVG_NS = "http://www.w3.org/2000/svg";
1212
const DEFAULT_VIEWPORT = Object.freeze({ height: 220, width: 320, x: 0, y: 0, zoom: 1 });
13-
const GRID_STEP = 10;
13+
const GRID_STEP = 1;
1414
const MAX_ZOOM = 4;
1515
const MIN_ZOOM = 0.25;
1616
const ZOOM_STEP = 0.1;

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1258,11 +1258,13 @@ textarea:hover {
12581258
}
12591259

12601260
.object-vector-studio-v2__render-surface {
1261-
width: min(100%, 480px);
1261+
width: 100%;
1262+
max-width: 100%;
12621263
height: auto;
12631264
min-height: 0;
12641265
aspect-ratio: 16 / 11;
12651266
align-self: stretch;
1267+
justify-self: stretch;
12661268
border: 1px solid var(--tool-starter-line);
12671269
border-radius: 8px;
12681270
background: rgba(0, 0, 0, 0.16);
@@ -1276,6 +1278,7 @@ textarea:hover {
12761278
.object-vector-studio-v2__svg-grid {
12771279
stroke: rgba(255, 255, 255, 0.18);
12781280
stroke-width: 1;
1281+
vector-effect: non-scaling-stroke;
12791282
pointer-events: none;
12801283
}
12811284

0 commit comments

Comments
 (0)