Skip to content

Commit f60bff7

Browse files
author
DavidQ
committed
Preserve Object Vector Studio V2 scroll state and finalize object panel cleanup consistency - PR_26133_001-object-vector-studio-scroll-persistence
1 parent ddba1c3 commit f60bff7

7 files changed

Lines changed: 137 additions & 15 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# PR_26133_001 Playwright V8 Coverage Report
2+
3+
Coverage source: `docs/dev/reports/playwright_v8_coverage.txt`, refreshed by the Workspace V2 Playwright run.
4+
5+
## Summary
6+
7+
- Coverage is advisory only; no thresholds are enforced.
8+
- 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 97/97, executed functions 4/5.
11+
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 90%, executed lines 3187/3187, executed functions 331/369.
12+
- The coverage report lists no low-coverage changed runtime JS files.
13+
14+
## Validation Context
15+
16+
- Main command: `npm run test:workspace-v2`.
17+
- Result: 45 passed.
18+
- Focused Object Vector Studio V2 layout/scroll contract test also passed.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# PR_26133_001 Workspace V2 Results
2+
3+
## Command Results
4+
5+
- `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`: passed.
6+
- `node --check tools/object-vector-studio-v2/js/bootstrap.js`: passed.
7+
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`: passed.
8+
- `node -e` JSON parse for `games/Asteroids/game.manifest.json` and `tools/schemas/tools/object-vector-studio-v2.schema.json`: passed.
9+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "shows Object Vector Studio V2 layout shell"`: 1 passed.
10+
- `npm run test:workspace-v2`: 45 passed.
11+
- `git diff --check`: passed.
12+
13+
## Targeted Object Vector Studio V2 Verification
14+
15+
- Standalone Object Vector Studio V2 Playwright manual pass reported `consoleErrors: []` and `pageErrors: []`.
16+
- Object selection preserved left-panel and Objects accordion scroll: before `{ leftPanel: 30, objects: 36 }`, after `{ leftPanel: 30, objects: 36 }`.
17+
- Shape selection preserved left-panel and Objects accordion scroll: before `{ leftPanel: 30, objects: 36 }`, after `{ leftPanel: 30, objects: 36 }`.
18+
- Shape/Tools content reached its accordion bottom.
19+
- Object Name and Tag rows were inline.
20+
- Shape row density measured `11.2px` font size and `22px` row height.
21+
- Left panel was scrollable during the verification pass.
22+
23+
## Contract Checks
24+
25+
- `objectType` UI controls remain absent from Object Vector Studio V2.
26+
- Object-level `type` drift remains rejected by the Object Vector Studio V2 schema path.
27+
- Exact Asteroids Object Vector `asteroids` tag search found no persisted tag values.
28+
- Existing Object Vector Studio V2 payload contracts remain schema-first with no `imageDataUrl` persistence.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,14 +1563,60 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15631563
await expect(page.locator("#statusLog")).toHaveValue(/WARN Create line blocked: object Asteroids Ship is locked for this runtime session\./);
15641564
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='lock']").click();
15651565
await expect(page.locator("#objectVectorStudioV2RenameObjectButton")).toBeEnabled();
1566-
const shapeScrollBefore = await page.locator("#objectVectorStudioV2ObjectsContent").evaluate((element) => {
1567-
element.scrollTop = 32;
1568-
return element.scrollTop;
1566+
1567+
const forceLeftPanelScroll = async () => page.evaluate(() => {
1568+
const leftPanel = document.querySelector(".tool-starter__panel--left");
1569+
const objectsContent = document.querySelector("#objectVectorStudioV2ObjectsContent");
1570+
if (!leftPanel.dataset.scrollPersistenceStylesCaptured) {
1571+
leftPanel.dataset.scrollPersistenceStylesCaptured = "true";
1572+
leftPanel.dataset.scrollPersistenceHeight = leftPanel.style.height;
1573+
leftPanel.dataset.scrollPersistenceMaxHeight = leftPanel.style.maxHeight;
1574+
leftPanel.dataset.scrollPersistenceOverflowY = leftPanel.style.overflowY;
1575+
}
1576+
leftPanel.style.height = "150px";
1577+
leftPanel.style.maxHeight = "150px";
1578+
leftPanel.style.overflowY = "auto";
1579+
leftPanel.scrollTop = 24;
1580+
objectsContent.scrollTop = 32;
1581+
return {
1582+
leftPanelScrollTop: leftPanel.scrollTop,
1583+
objectsScrollTop: objectsContent.scrollTop
1584+
};
15691585
});
1586+
const readLeftPanelScroll = async () => page.evaluate(() => {
1587+
const leftPanel = document.querySelector(".tool-starter__panel--left");
1588+
const objectsContent = document.querySelector("#objectVectorStudioV2ObjectsContent");
1589+
return {
1590+
leftPanelScrollTop: leftPanel.scrollTop,
1591+
objectsScrollTop: objectsContent.scrollTop
1592+
};
1593+
});
1594+
const restoreLeftPanelStyle = async () => page.evaluate(() => {
1595+
const leftPanel = document.querySelector(".tool-starter__panel--left");
1596+
leftPanel.style.height = leftPanel.dataset.scrollPersistenceHeight || "";
1597+
leftPanel.style.maxHeight = leftPanel.dataset.scrollPersistenceMaxHeight || "";
1598+
leftPanel.style.overflowY = leftPanel.dataset.scrollPersistenceOverflowY || "";
1599+
delete leftPanel.dataset.scrollPersistenceStylesCaptured;
1600+
delete leftPanel.dataset.scrollPersistenceHeight;
1601+
delete leftPanel.dataset.scrollPersistenceMaxHeight;
1602+
delete leftPanel.dataset.scrollPersistenceOverflowY;
1603+
});
1604+
1605+
const leftPanelObjectScrollBefore = await forceLeftPanelScroll();
1606+
expect(leftPanelObjectScrollBefore.leftPanelScrollTop).toBeGreaterThan(0);
1607+
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-2']").evaluate((tile) => tile.click());
1608+
await expect.poll(readLeftPanelScroll).toEqual(leftPanelObjectScrollBefore);
1609+
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-2']")).toHaveClass(/is-selected/);
1610+
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1']").evaluate((tile) => tile.click());
1611+
await expect.poll(readLeftPanelScroll).toEqual(leftPanelObjectScrollBefore);
1612+
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1']")).toHaveClass(/is-selected/);
1613+
1614+
const shapeScrollBefore = await forceLeftPanelScroll();
15701615
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-tile-shape-id='rectangle-1']").evaluate((button) => button.click());
1571-
await expect.poll(async () => page.locator("#objectVectorStudioV2ObjectsContent").evaluate((element) => element.scrollTop)).toBe(shapeScrollBefore);
1616+
await expect.poll(readLeftPanelScroll).toEqual(shapeScrollBefore);
15721617
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='rectangle-1']")).toHaveClass(/is-selected/);
15731618
await expect(page.locator("#statusLog")).toHaveValue(/OK Selected shape from object tile shape list: rectangle-1 \(rectangle\)\./);
1619+
await restoreLeftPanelStyle();
15741620

15751621
await page.locator("[data-palette-color='#6fd3ff']").click();
15761622
await expect(page.locator("[data-palette-color='#6fd3ff']")).toHaveClass(/is-selected/);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
156156
</section>
157157

158158
<aside class="tool-starter__panel tool-starter__panel--right" aria-label="Tool output">
159-
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
159+
<section class="accordion-v2 tool-starter__accordion tool-starter__accordion--fill is-open" data-accordion-v2-open="true">
160160
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="objectVectorStudioV2ShapeToolsContent">
161161
<span>Shape/Tools</span>
162162
<span class="accordion-v2__icon" aria-hidden="true">+</span>

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -930,8 +930,24 @@ export class ToolStarterApp {
930930
}
931931

932932
restoreObjectsScroll(scrollTop) {
933-
const maxScrollTop = Math.max(0, this.elements.objectsContent.scrollHeight - this.elements.objectsContent.clientHeight);
934-
this.elements.objectsContent.scrollTop = Math.min(scrollTop, maxScrollTop);
933+
this.restoreElementScrollTop(this.elements.objectsContent, scrollTop);
934+
}
935+
936+
captureLeftPanelScrollState() {
937+
return {
938+
leftPanelScrollTop: this.elements.leftPanel.scrollTop,
939+
objectsScrollTop: this.elements.objectsContent.scrollTop
940+
};
941+
}
942+
943+
restoreLeftPanelScrollState(scrollState) {
944+
this.restoreElementScrollTop(this.elements.leftPanel, scrollState.leftPanelScrollTop);
945+
this.restoreElementScrollTop(this.elements.objectsContent, scrollState.objectsScrollTop);
946+
}
947+
948+
restoreElementScrollTop(element, scrollTop) {
949+
const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
950+
element.scrollTop = Math.min(scrollTop, maxScrollTop);
935951
}
936952

937953
createObjectTileControl(object, kind) {
@@ -1850,6 +1866,7 @@ export class ToolStarterApp {
18501866
return;
18511867
}
18521868

1869+
const scrollState = this.captureLeftPanelScrollState();
18531870
this.selectedObjectId = objectId;
18541871
const selectedObject = this.selectedObject();
18551872
this.selectedShapeId = sortedShapes(selectedObject)[0]?.id || "";
@@ -1858,6 +1875,7 @@ export class ToolStarterApp {
18581875
this.selectedStateId = selectedState?.id || "";
18591876
this.selectedFrameId = selectedState ? sortedFrames(selectedState)[0]?.id || "" : "";
18601877
this.renderPayload();
1878+
this.restoreLeftPanelScrollState(scrollState);
18611879
const selected = this.selectedObject();
18621880
this.statusLog.write(`OK Selected object from ${sourceLabel}: ${selected.name}.`);
18631881
}
@@ -1869,6 +1887,7 @@ export class ToolStarterApp {
18691887
return;
18701888
}
18711889

1890+
const scrollState = this.captureLeftPanelScrollState();
18721891
this.selectedShapeId = shapeId;
18731892
if (options.additive) {
18741893
if (this.selectedShapeIds.has(shapeId) && this.selectedShapeIds.size > 1) {
@@ -1881,6 +1900,7 @@ export class ToolStarterApp {
18811900
this.selectedShapeIds = new Set([shapeId]);
18821901
}
18831902
this.renderPayload();
1903+
this.restoreLeftPanelScrollState(scrollState);
18841904
const shape = this.selectedShape();
18851905
this.statusLog.write(`OK Selected shape from ${sourceLabel}: ${shape.id} (${shape.type}). Multi-select count: ${this.selectedShapeIds.size}.`);
18861906
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ window.addEventListener("DOMContentLoaded", () => {
5050
gridSnapButton: requireElement("#objectVectorStudioV2GridSnapButton"),
5151
groupShapesButton: requireElement("#objectVectorStudioV2GroupShapesButton"),
5252
jsonDetails: requireElement("#objectVectorStudioV2JsonDetails"),
53+
leftPanel: requireElement(".tool-starter__panel--left"),
5354
loopToggle: requireElement("#objectVectorStudioV2LoopToggle"),
5455
objectDetails: requireElement("#objectVectorStudioV2ObjectDetails"),
5556
objectDetailsCount: requireElement("#objectVectorStudioV2ObjectDetailsCount"),

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ textarea:hover {
198198
overflow-y: auto;
199199
}
200200

201+
.tool-starter__panel--left > .accordion-v2.is-open {
202+
flex: 1 0 220px;
203+
}
204+
201205
.accordion-v2 {
202206
--accordion-v2-accent: var(--tool-starter-accent);
203207
--accordion-v2-line: var(--tool-starter-line);
@@ -328,6 +332,10 @@ textarea:hover {
328332
padding: 7px 9px;
329333
}
330334

335+
#objectVectorStudioV2ObjectContent .tool-starter__field--compact > span {
336+
white-space: nowrap;
337+
}
338+
331339
.tool-starter__field input[readonly] {
332340
color: var(--tool-starter-muted);
333341
cursor: default;
@@ -384,8 +392,9 @@ textarea:hover {
384392
}
385393

386394
#objectVectorStudioV2ShapeToolsContent {
387-
flex: 1 1 auto;
395+
flex: 1 1 0;
388396
max-height: none;
397+
min-height: 0;
389398
}
390399

391400
.object-vector-studio-v2__palette-swatch-list {
@@ -908,27 +917,27 @@ textarea:hover {
908917
.object-vector-studio-v2__object-tile-shapes {
909918
grid-column: 1 / -1;
910919
display: grid;
911-
gap: 2px;
920+
gap: 1px;
912921
border-top: 1px solid var(--tool-starter-line);
913-
padding-top: 5px;
922+
padding-top: 4px;
914923
}
915924

916925
.object-vector-studio-v2__object-tile-shape-row {
917926
display: block;
918-
font-size: 0.74rem;
919-
line-height: 1.12;
927+
font-size: 0.7rem;
928+
line-height: 1.08;
920929
}
921930

922931
.object-vector-studio-v2__shape-select {
923932
width: 100% !important;
924-
min-height: 24px;
933+
min-height: 22px;
925934
position: relative;
926935
z-index: 3;
927936
display: flex;
928937
align-items: center;
929938
justify-content: space-between;
930-
gap: 8px;
931-
padding: 3px 6px !important;
939+
gap: 6px;
940+
padding: 2px 5px !important;
932941
text-align: left;
933942
}
934943

0 commit comments

Comments
 (0)