Skip to content

Commit 48f4b09

Browse files
author
DavidQ
committed
Preserve Objects hierarchy scroll position and tighten shape hierarchy density in Object Vector Studio V2 - PR_26132_022-object-vector-studio-v2-objects-scroll-density
1 parent d0cf77a commit 48f4b09

6 files changed

Lines changed: 134 additions & 17 deletions

File tree

docs/dev/reports/playwright_v8_coverage.txt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ Exercised tool entry points detected:
2020
(0%) Workspace Manager - not exercised by this Playwright run
2121

2222
Changed runtime JS files covered:
23-
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 98/98; executed functions 4/5
24-
(90%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 3209/3209; executed functions 334/372
25-
(95%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - executed lines 401/401; executed functions 52/55
26-
(98%) src/engine/rendering/ObjectVectorRuntimeAssetService.js - executed lines 914/914; executed functions 105/107
23+
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 99/99; executed functions 4/5
24+
(90%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 3217/3217; executed functions 335/373
2725

2826
Files with executed line/function counts where available:
2927
(2%) src/engine/input/ActionInputService.js - executed lines 397/397; executed functions 1/51
@@ -169,7 +167,7 @@ Files with executed line/function counts where available:
169167
(80%) src/engine/persistence/StorageService.js - executed lines 49/49; executed functions 4/5
170168
(80%) tools/asset-manager-v2/js/controls/AccordionSection.js - executed lines 27/27; executed functions 4/5
171169
(80%) tools/asset-manager-v2/js/controls/AssetFormControl.js - executed lines 563/563; executed functions 49/61
172-
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 98/98; executed functions 4/5
170+
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 99/99; executed functions 4/5
173171
(80%) tools/palette-manager-v2/modules/PaletteHistoryStack.js - executed lines 54/54; executed functions 8/10
174172
(80%) tools/preview-generator-v2/controls/AccordionSection.js - executed lines 31/31; executed functions 4/5
175173
(80%) tools/preview-generator-v2/PreviewGeneratorV2Logger.js - executed lines 19/19; executed functions 4/5
@@ -195,7 +193,7 @@ Files with executed line/function counts where available:
195193
(88%) tools/world-vector-studio-v2/js/controls/SourceInputControl.js - executed lines 33/33; executed functions 7/8
196194
(89%) tools/asset-manager-v2/js/services/AssetSchemaValidator.js - executed lines 295/295; executed functions 25/28
197195
(89%) tools/preview-generator-v2/controls/StatusLogControl.js - executed lines 32/32; executed functions 8/9
198-
(90%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 3209/3209; executed functions 334/372
196+
(90%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 3217/3217; executed functions 335/373
199197
(90%) tools/palette-manager-v2/modules/PaletteValidationService.js - executed lines 88/88; executed functions 9/10
200198
(90%) tools/text2speech-V2/js/controls/ActionNavControl.js - executed lines 117/117; executed functions 19/21
201199
(90%) tools/text2speech-V2/js/TextToSpeechToolApp.js - executed lines 807/807; executed functions 62/69
@@ -295,8 +293,5 @@ Uncovered or low-coverage changed JS files:
295293

296294
Changed JS files considered:
297295
(0%) tests/playwright/tools/WorkspaceManagerV2.spec.mjs - changed JS file not collected as browser runtime coverage
298-
(0%) tools/object-vector-studio-v2/tests/playwright/FirstClassToolStarter.spec.mjs - changed JS file not collected as browser runtime coverage
299296
(80%) tools/object-vector-studio-v2/js/bootstrap.js - changed JS file with browser V8 coverage
300297
(90%) tools/object-vector-studio-v2/js/ToolStarterApp.js - changed JS file with browser V8 coverage
301-
(95%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - changed JS file with browser V8 coverage
302-
(98%) src/engine/rendering/ObjectVectorRuntimeAssetService.js - changed JS file with browser V8 coverage
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# PR_26132_022-object-vector-studio-v2-objects-scroll-density
2+
3+
## Scope
4+
5+
Updates Object Vector Studio V2 Objects hierarchy scrolling and row density only. No schema changes, no unrelated controls, and no sample JSON changes.
6+
7+
## Changes
8+
9+
- Preserved `objectVectorStudioV2ObjectsContent` scroll position across object and shape selection re-renders.
10+
- Added an explicit Objects content element binding so render code restores the correct scroll container.
11+
- Tightened object hierarchy spacing by reducing shape-list gaps, padding, row height, and metadata line height.
12+
- Reduced shape row font sizing while keeping labels readable.
13+
- Shortened shape hierarchy labels under selected objects to dense row labels such as `0. small-ufo-body` and `1. small-ufo-canopy`.
14+
- Kept the object-level label such as `objects > Small UFO` intact on the selected object tile.
15+
16+
## Validation
17+
18+
Playwright impacted: Yes.
19+
20+
Commands run:
21+
22+
- `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`
23+
- `node --check tools/object-vector-studio-v2/js/bootstrap.js`
24+
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`
25+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "shows Object Vector Studio V2 layout shell"`
26+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "uses header lifecycle controls"`
27+
- `npm run test:workspace-v2`
28+
29+
Result:
30+
31+
- Focused Object Vector Studio V2 hierarchy coverage passed.
32+
- Focused Workspace Manager launch coverage passed.
33+
- Full Workspace Manager V2 suite passed: 45 passed.
34+
- Playwright V8 coverage refreshed at `docs/dev/reports/playwright_v8_coverage.txt`.
35+
- Full samples smoke test skipped per request.
36+
37+
## Playwright Coverage
38+
39+
Validates:
40+
41+
- Objects scroll position persists when selecting a shape from the hierarchy.
42+
- Objects scroll position persists when selecting another object.
43+
- Shape hierarchy rows use reduced font sizing, line height, row height, and spacing.
44+
- Workspace-launched Asteroids Object Vector payload keeps `objects > Small UFO` and renders compact shape rows `0. small-ufo-body` and `1. small-ufo-canopy`.
45+
46+
Expected pass behavior:
47+
48+
- Object Vector Studio V2 selection updates highlight state and details without jumping the Objects container scroll position.
49+
- Shape hierarchy rows are visibly denser while staying readable.
50+
51+
Expected fail behavior:
52+
53+
- Invalid payloads and blocked actions continue using existing visible Status Log WARN/FAIL behavior without partial render or silent fallback.
54+
55+
## Manual Validation
56+
57+
1. Open `tools/object-vector-studio-v2/index.html`.
58+
2. Load a valid Object Vector payload with enough objects to scroll the Objects accordion.
59+
3. Scroll the Objects accordion downward, then select a visible object or shape.
60+
4. Confirm the scroll position remains stable after the selected state updates.
61+
5. Select an object with multiple shapes, such as Small UFO from the Asteroids workspace payload.
62+
6. Confirm the object label remains readable and shape rows are compact, for example `0. small-ufo-body` and `1. small-ufo-canopy`.
63+
64+
Expected outcome:
65+
66+
- The Objects hierarchy remains stable during selection and displays more rows in the same vertical space.
67+
68+
## Out Of Scope
69+
70+
- No schema changes.
71+
- No unrelated controls.
72+
- No sample JSON changes.
73+
- No full samples smoke test.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1469,7 +1469,24 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14691469
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='circle-2']")).toHaveClass(/is-selected/);
14701470
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1']")).toHaveClass(/is-selected/);
14711471
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-tile-shape-id]")).toHaveCount(2);
1472+
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-tile-shape-id] .object-vector-studio-v2__shape-select-label")).toHaveText(["1. rectangle-1", "2. circle-2"]);
14721473
await expect(page.locator(".object-vector-studio-v2__object-tile-shape-row.is-selected [data-object-tile-shape-id='circle-2']")).toHaveCount(1);
1474+
const shapeHierarchyDensity = await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1']").evaluate((tile) => {
1475+
const shapeList = tile.querySelector(".object-vector-studio-v2__object-tile-shapes");
1476+
const shapeRow = tile.querySelector(".object-vector-studio-v2__object-tile-shape-row");
1477+
const shapeButton = tile.querySelector("[data-object-tile-shape-id]");
1478+
const shapeLabel = tile.querySelector(".object-vector-studio-v2__shape-select-label");
1479+
return {
1480+
fontSize: Number.parseFloat(getComputedStyle(shapeRow).fontSize),
1481+
gap: Number.parseFloat(getComputedStyle(shapeList).gap),
1482+
labelLineHeight: Number.parseFloat(getComputedStyle(shapeLabel).lineHeight),
1483+
rowHeight: Math.round(shapeButton.getBoundingClientRect().height)
1484+
};
1485+
});
1486+
expect(shapeHierarchyDensity.fontSize).toBeLessThanOrEqual(12);
1487+
expect(shapeHierarchyDensity.gap).toBeLessThanOrEqual(3);
1488+
expect(shapeHierarchyDensity.labelLineHeight).toBeLessThanOrEqual(14);
1489+
expect(shapeHierarchyDensity.rowHeight).toBeLessThanOrEqual(28);
14731490
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-control='visibility']")).toHaveText("");
14741491
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-control='lock']")).toHaveText("");
14751492
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-control='visibility'] .object-vector-studio-v2__tile-icon--eye")).toHaveCount(1);
@@ -1487,7 +1504,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14871504
await expect(page.locator("#statusLog")).toHaveValue(/WARN Create line blocked: object Asteroids Ship is locked for this runtime session\./);
14881505
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-control='lock']").click();
14891506
await expect(page.locator("#objectVectorStudioV2RenameObjectButton")).toBeEnabled();
1490-
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-tile-shape-id='rectangle-1']").click();
1507+
const shapeScrollBefore = await page.locator("#objectVectorStudioV2ObjectsContent").evaluate((element) => {
1508+
element.scrollTop = 32;
1509+
return element.scrollTop;
1510+
});
1511+
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object-1'] [data-object-tile-shape-id='rectangle-1']").evaluate((button) => button.click());
1512+
await expect.poll(async () => page.locator("#objectVectorStudioV2ObjectsContent").evaluate((element) => element.scrollTop)).toBe(shapeScrollBefore);
14911513
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='rectangle-1']")).toHaveClass(/is-selected/);
14921514
await expect(page.locator("#statusLog")).toHaveValue(/OK Selected shape from object tile shape list: rectangle-1 \(rectangle\)\./);
14931515

@@ -1681,7 +1703,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16811703
const exportedSchemaValidation = await page.evaluate((payload) => window.__objectVectorStudioV2App.schemaService.validatePayload(payload), exportedPayload);
16821704
expect(exportedSchemaValidation).toEqual({ errors: [], ok: true, payload: exportedPayload });
16831705

1706+
const objectScrollBefore = await page.locator("#objectVectorStudioV2ObjectsContent").evaluate((element) => {
1707+
element.scrollTop = 120;
1708+
return element.scrollTop;
1709+
});
16841710
await page.locator('.object-vector-studio-v2__object-tile[data-object-id="object-2"]').evaluate((button) => button.click());
1711+
await expect.poll(async () => page.locator("#objectVectorStudioV2ObjectsContent").evaluate((element) => element.scrollTop)).toBe(objectScrollBefore);
16851712
await expect(page.locator('[data-object-id="object-2"]')).toHaveAttribute("aria-pressed", "true");
16861713
await expect(page.locator("#objectVectorStudioV2ObjectNameInput")).toHaveValue("Object 2");
16871714
await expect(page.locator("#objectVectorStudioV2ObjectPreviewFooter")).toContainText("Object ID: object-2");
@@ -5334,6 +5361,11 @@ test.describe("Workspace Manager V2 bootstrap", () => {
53345361
await expect(page.locator("#objectVectorStudioV2ObjectTiles")).toContainText("Asteroids Ship");
53355362
await expect(page.locator("#objectVectorStudioV2ObjectTiles")).toContainText("Large Asteroid");
53365363
await expect(page.locator("#objectVectorStudioV2ObjectTiles")).toContainText("Large UFO");
5364+
const smallUfoTile = page.locator('.object-vector-studio-v2__object-tile[data-object-id="object.asteroids.ufo.small"]');
5365+
await smallUfoTile.scrollIntoViewIfNeeded();
5366+
await smallUfoTile.click();
5367+
await expect(smallUfoTile).toContainText("objects > Small UFO");
5368+
await expect(smallUfoTile.locator("[data-object-tile-shape-id] .object-vector-studio-v2__shape-select-label")).toHaveText(["0. small-ufo-body", "1. small-ufo-canopy"]);
53375369
await page.locator('button[aria-controls="objectVectorStudioV2DependencyGraphContent"]').click();
53385370
await expect(page.locator("#objectVectorStudioV2DependencyGraph")).toContainText("Deferred reusable library capability");
53395371
await expect(page.locator("#objectVectorStudioV2DependencyGraph")).toContainText("asset.asteroids.ship");

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,10 +860,12 @@ export class ToolStarterApp {
860860
}
861861

862862
renderObjectTiles() {
863+
const previousScrollTop = this.elements.objectsContent.scrollTop;
863864
this.updateObjectDetailsHeader(this.currentPayload.objects.length, this.selectedObject()?.shapes.length || 0);
864865
this.elements.objectTiles.replaceChildren();
865866
if (!this.currentPayload.objects.length) {
866867
this.elements.objectTiles.append(this.createEmptyObjectTile());
868+
this.restoreObjectsScroll(previousScrollTop);
867869
return;
868870
}
869871

@@ -872,6 +874,7 @@ export class ToolStarterApp {
872874
const tile = this.createEmptyObjectTile();
873875
tile.textContent = "No objects match the current tag or search filter.";
874876
this.elements.objectTiles.append(tile);
877+
this.restoreObjectsScroll(previousScrollTop);
875878
return;
876879
}
877880

@@ -925,6 +928,12 @@ export class ToolStarterApp {
925928
}
926929
this.elements.objectTiles.append(tile);
927930
});
931+
this.restoreObjectsScroll(previousScrollTop);
932+
}
933+
934+
restoreObjectsScroll(scrollTop) {
935+
const maxScrollTop = Math.max(0, this.elements.objectsContent.scrollHeight - this.elements.objectsContent.clientHeight);
936+
this.elements.objectsContent.scrollTop = Math.min(scrollTop, maxScrollTop);
928937
}
929938

930939
createObjectTileControl(object, kind) {
@@ -972,7 +981,7 @@ export class ToolStarterApp {
972981
selectButton.setAttribute("aria-pressed", String(this.selectedShapeIds.has(shape.id)));
973982
const label = document.createElement("span");
974983
label.className = "object-vector-studio-v2__shape-select-label";
975-
label.textContent = `objects > ${object.name} > ${shape.order}. ${shape.id}`;
984+
label.textContent = `${shape.order}. ${shape.id}`;
976985
const eyeIcon = this.createIconSpan("eye", shape.visible);
977986
eyeIcon.classList.add("object-vector-studio-v2__shape-eye-inline");
978987
eyeIcon.dataset.shapeVisibilityId = shape.id;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ window.addEventListener("DOMContentLoaded", () => {
5959
objectTagList: requireElement("#objectVectorStudioV2ObjectTagList"),
6060
objectTypeInput: requireElement("#objectVectorStudioV2ObjectTypeInput"),
6161
objectTypeOptions: requireElement("#objectVectorStudioV2ObjectTypeOptions"),
62+
objectsContent: requireElement("#objectVectorStudioV2ObjectsContent"),
6263
objectTiles: requireElement("#objectVectorStudioV2ObjectTiles"),
6364
paintModeButton: requireElement("#objectVectorStudioV2PaintModeButton"),
6465
paletteSortButtons: Array.from(document.querySelectorAll("[data-palette-sort]")),

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,7 @@ textarea:hover {
856856
.object-vector-studio-v2__object-tile-copy {
857857
min-width: 0;
858858
display: grid;
859-
gap: 4px;
859+
gap: 2px;
860860
}
861861

862862
.object-vector-studio-v2__object-select {
@@ -882,8 +882,7 @@ textarea:hover {
882882
gap: 4px;
883883
}
884884

885-
.object-vector-studio-v2__object-tile-control,
886-
.object-vector-studio-v2__object-tile-shape-row button {
885+
.object-vector-studio-v2__object-tile-control {
887886
position: relative;
888887
z-index: 3;
889888
width: 34px;
@@ -895,21 +894,27 @@ textarea:hover {
895894
.object-vector-studio-v2__object-tile-shapes {
896895
grid-column: 1 / -1;
897896
display: grid;
898-
gap: 6px;
897+
gap: 2px;
899898
border-top: 1px solid var(--tool-starter-line);
900-
padding-top: 8px;
899+
padding-top: 5px;
901900
}
902901

903902
.object-vector-studio-v2__object-tile-shape-row {
904903
display: block;
904+
font-size: 0.74rem;
905+
line-height: 1.12;
905906
}
906907

907908
.object-vector-studio-v2__shape-select {
908909
width: 100% !important;
910+
min-height: 24px;
911+
position: relative;
912+
z-index: 3;
909913
display: flex;
910914
align-items: center;
911915
justify-content: space-between;
912916
gap: 8px;
917+
padding: 3px 6px !important;
913918
text-align: left;
914919
}
915920

@@ -1006,11 +1011,13 @@ textarea:hover {
10061011

10071012
.object-vector-studio-v2__object-tile-name {
10081013
font-weight: 800;
1014+
line-height: 1.14;
10091015
}
10101016

10111017
.object-vector-studio-v2__object-tile-meta {
10121018
color: var(--tool-starter-muted);
1013-
font-size: 0.88rem;
1019+
font-size: 0.78rem;
1020+
line-height: 1.16;
10141021
overflow-wrap: anywhere;
10151022
}
10161023

0 commit comments

Comments
 (0)