Skip to content

Commit 9690eda

Browse files
author
DavidQ
committed
Add palette tag sorting and fix Paint Stroke mode based color application - PR_26133_058-palette-tag-sort-and-paint-stroke-mode-flow
1 parent de8870f commit 9690eda

6 files changed

Lines changed: 108 additions & 53 deletions

File tree

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_057 Playwright V8 Coverage Report
1+
# PR_26133_058 Playwright V8 Coverage Report
22

3-
Task: PR_26133_057-group-rotate-transform-behavior
3+
Task: PR_26133_058-palette-tag-sort-and-paint-stroke-mode-flow
44
Date: 2026-05-15
55

66
## Result
@@ -24,7 +24,7 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
2424
## Relevant Runtime Coverage
2525

2626
```text
27-
(95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 5676/5676; executed functions 599/631
27+
(95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 5688/5688; executed functions 600/631
2828
```
2929

3030
## Guardrail
@@ -35,4 +35,4 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
3535

3636
## PR-Specific Note
3737

38-
The Workspace V2 run exercised Object Vector Studio V2 group-aware Rotate behavior, grouped transform validation, selected-shape independent Rotate behavior, refreshed preview bounds/handles, schema validation, and Asteroids runtime object-vector rendering.
38+
The Workspace V2 run exercised Object Vector Studio V2 Palette Tag sorting, compact five-button sort layout, Paint/Stroke mode selection, swatch-only color selection, shape-click color application, schema validation, and Asteroids runtime object-vector rendering.
Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_057 Workspace V2 Playwright Results
1+
# PR_26133_058 Workspace V2 Playwright Results
22

3-
Task: PR_26133_057-group-rotate-transform-behavior
3+
Task: PR_26133_058-palette-tag-sort-and-paint-stroke-mode-flow
44
Date: 2026-05-15
55

66
## Result
@@ -14,13 +14,18 @@ PASS - `npm run test:workspace-v2` completed successfully.
1414

1515
## PR-Specific Coverage
1616

17-
- Verified Rotate applies to every shape in the selected group when the selected shape belongs to a valid group.
18-
- Verified group rotation preserves relative origin spacing while rotating around the selected shape pivot/origin.
19-
- Verified non-grouped shapes keep the existing independent Rotate behavior.
20-
- Verified preview rendering, selection bounds, and resize handles refresh after grouped rotation.
17+
- Verified Tag appears as the fifth Palette sort option and sorts swatches by swatch tags.
18+
- Verified Hue/Sat/Bri/Name/Tag sort controls stay on one line.
19+
- Verified Paint and Stroke buttons select mode only and do not directly recolor the selected shape.
20+
- Verified swatch clicks update the active Paint/Stroke color without directly recoloring the selected shape.
21+
- Verified clicking a shape applies the currently selected Paint/Stroke color.
2122

2223
## Additional Validation
2324

24-
- Focused group/state transform slice passed:
25-
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "single-member groups"` completed with 1 passed, 0 failed.
26-
- `git diff --check` passed. The command reported the existing Windows LF-to-CRLF warning for `tests/playwright/tools/WorkspaceManagerV2.spec.mjs` and no whitespace errors.
25+
- Focused palette/layout slice passed:
26+
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "layout shell"` completed with 1 passed, 0 failed.
27+
- Focused Object Vector dirty-state slice passed:
28+
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "dirty state"` completed with 1 passed, 0 failed.
29+
- Focused Asteroids runtime asset slice passed:
30+
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "runtime assets into Asteroids"` completed with 1 passed, 0 failed.
31+
- `git diff --check` passed. The command reported existing Windows LF-to-CRLF warnings for touched files and no whitespace errors.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
13431343
bri: icon("[data-palette-sort='bri']"),
13441344
hue: icon("[data-palette-sort='hue']"),
13451345
name: icon("[data-palette-sort='name']"),
1346-
sat: icon("[data-palette-sort='sat']")
1346+
sat: icon("[data-palette-sort='sat']"),
1347+
tag: icon("[data-palette-sort='tag']")
13471348
},
13481349
paletteSortLayout: {
13491350
buttonFontSizes: paletteSortButtonStyles.map((style) => Number.parseFloat(style.fontSize)),
@@ -1432,7 +1433,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14321433
bri: { iconKey: "bri", iconName: "nf-fa-sun_o" },
14331434
hue: { iconKey: "hue", iconName: "nf-fa-eyedropper" },
14341435
name: { iconKey: "name", iconName: "nf-fa-font" },
1435-
sat: { iconKey: "sat", iconName: "nf-fa-tint" }
1436+
sat: { iconKey: "sat", iconName: "nf-fa-tint" },
1437+
tag: { iconKey: "tag", iconName: "nf-fa-tag" }
14361438
});
14371439
expect(Object.fromEntries(Object.entries(iconStyleState.shapeIcons).map(([key, value]) => [key, value.iconKey]))).toEqual({
14381440
arc: "arc",
@@ -1712,8 +1714,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17121714
sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
17131715
id: "arcade-primary",
17141716
swatches: [
1715-
{ id: "white", value: "#ffffff" },
1716-
{ id: "cyan", value: "#6fd3ff" }
1717+
{ id: "white", tags: ["zeta"], value: "#ffffff" },
1718+
{ id: "cyan", tags: ["alpha"], value: "#6fd3ff" }
17171719
]
17181720
}));
17191721
});
@@ -1767,7 +1769,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17671769
widthIsRightOfStroke: true
17681770
});
17691771
await expect(page.locator(".object-vector-studio-v2__palette-sort")).not.toContainText("Sort");
1770-
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue◇", "Sat◇", "Bri◇", "Name▲"]);
1772+
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue◇", "Sat◇", "Bri◇", "Name▲", "Tag◇"]);
17711773
await expect(page.locator("[data-palette-sort='name']")).toHaveAttribute("data-sort-direction", "asc");
17721774
const swatchState = await page.locator(".object-vector-studio-v2__palette-swatch").evaluateAll((swatches) => swatches.map((swatch) => {
17731775
const rect = swatch.getBoundingClientRect();
@@ -1784,14 +1786,25 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17841786
{ ariaLabel: "Palette swatch white #ffffff", height: 32, text: "", title: "white\n#ffffff", width: 32 }
17851787
]);
17861788
await page.locator("[data-palette-sort='name']").click();
1787-
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue◇", "Sat◇", "Bri◇", "Name▼"]);
1789+
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue◇", "Sat◇", "Bri◇", "Name▼", "Tag◇"]);
17881790
await expect(page.locator(".object-vector-studio-v2__palette-swatch").first()).toHaveAttribute("data-palette-label", "white");
17891791
await expect(page.locator("#statusLog")).toHaveValue(/OK Palette sort set to name desc\./);
17901792
await page.locator("[data-palette-sort='hue']").click();
1791-
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue▲", "Sat◇", "Bri◇", "Name◇"]);
1793+
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue▲", "Sat◇", "Bri◇", "Name◇", "Tag◇"]);
17921794
await expect(page.locator("[data-palette-sort='hue']")).toHaveAttribute("data-sort-direction", "asc");
17931795
await expect(page.locator(".object-vector-studio-v2__palette-swatch").first()).toHaveAttribute("data-palette-label", "white");
17941796
await expect(page.locator("#statusLog")).toHaveValue(/OK Palette sort set to hue asc\./);
1797+
await page.locator("[data-palette-sort='tag']").click();
1798+
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue◇", "Sat◇", "Bri◇", "Name◇", "Tag▲"]);
1799+
await expect(page.locator("[data-palette-sort='tag']")).toHaveAttribute("data-sort-direction", "asc");
1800+
await expect(page.locator(".object-vector-studio-v2__palette-swatch").first()).toHaveAttribute("data-palette-tags", "alpha");
1801+
await expect(page.locator(".object-vector-studio-v2__palette-swatch").first()).toHaveAttribute("data-palette-label", "cyan");
1802+
await expect(page.locator("#statusLog")).toHaveValue(/OK Palette sort set to tag asc\./);
1803+
await page.locator("[data-palette-sort='tag']").click();
1804+
await expect(page.locator(".object-vector-studio-v2__palette-sort button")).toHaveText(["Hue◇", "Sat◇", "Bri◇", "Name◇", "Tag▼"]);
1805+
await expect(page.locator(".object-vector-studio-v2__palette-swatch").first()).toHaveAttribute("data-palette-tags", "zeta");
1806+
await expect(page.locator(".object-vector-studio-v2__palette-swatch").first()).toHaveAttribute("data-palette-label", "white");
1807+
await expect(page.locator("#statusLog")).toHaveValue(/OK Palette sort set to tag desc\./);
17951808
const compactAccordionLayout = await page.evaluate(() => {
17961809
const previewAccordion = document.querySelector(".object-vector-studio-v2__preview-accordion");
17971810
const previewContent = document.querySelector("#objectVectorStudioV2WorkAreaContent");
@@ -2256,6 +2269,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22562269
delete leftPanel.dataset.scrollPersistenceMaxHeight;
22572270
delete leftPanel.dataset.scrollPersistenceOverflowY;
22582271
});
2272+
const clickPreviewShape = async (shapeIndex, eventInit = {}) => {
2273+
await page.locator(`#objectVectorStudioV2RenderSurface [data-shape-index='${shapeIndex}']`).dispatchEvent("click", {
2274+
bubbles: true,
2275+
cancelable: true,
2276+
...eventInit
2277+
});
2278+
};
22592279

22602280
const leftPanelObjectScrollBefore = await forceLeftPanelScroll();
22612281
expect(leftPanelObjectScrollBefore.leftPanelScrollTop).toBeGreaterThan(0);
@@ -2273,10 +2293,20 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22732293
await expect(page.locator("#statusLog")).toHaveValue(/OK Selected shape from object tile shape list: row 0 \(rectangle\)\./);
22742294
await restoreLeftPanelStyle();
22752295

2296+
const shapeZeroStyleBeforePaintMode = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2297+
await page.locator("#objectVectorStudioV2PaintModeButton").click();
2298+
await expect(page.locator("#statusLog")).toHaveValue(/OK Palette target set to Paint\./);
2299+
const shapeZeroStyleAfterPaintMode = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2300+
expect(shapeZeroStyleAfterPaintMode).toEqual(shapeZeroStyleBeforePaintMode);
22762301
await page.locator("[data-palette-color='#6fd3ff']").click();
22772302
await expect(page.locator("[data-palette-color='#6fd3ff']")).toHaveClass(/is-selected/);
2303+
await expect(page.locator("#statusLog")).toHaveValue(/OK Selected paint color #6fd3ff from cyan\./);
2304+
const shapeZeroStyleAfterPaintSwatch = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2305+
expect(shapeZeroStyleAfterPaintSwatch).toEqual(shapeZeroStyleBeforePaintMode);
2306+
await clickPreviewShape(0);
22782307
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"fill": "#6fd3ff"');
2279-
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied palette color #6fd3ff from cyan to shape row 0 by palette swatch\./);
2308+
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied palette color #6fd3ff from cyan to shape row 0 by render surface click\. Target: paint opacity 1\./);
2309+
await page.locator('[data-shape-tool="select"]').click();
22802310
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-tile-shape-index='1']").click();
22812311
await expect(page.locator("[data-palette-color='#ffffff']")).toHaveClass(/is-selected/);
22822312
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-tile-shape-index='0']").click();
@@ -2303,14 +2333,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23032333
});
23042334
expect(selectionChrome).toEqual({ handleHeight: 3, handleWidth: 3, selectionStrokeWidth: "0.75px" });
23052335

2306-
const clickPreviewShape = async (shapeIndex, eventInit = {}) => {
2307-
await page.locator(`#objectVectorStudioV2RenderSurface [data-shape-index='${shapeIndex}']`).dispatchEvent("click", {
2308-
bubbles: true,
2309-
cancelable: true,
2310-
...eventInit
2311-
});
2312-
};
2313-
23142336
await clickPreviewShape(1, { shiftKey: true });
23152337
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='0']")).toHaveClass(/is-selected/);
23162338
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='1']")).toHaveClass(/is-selected/);
@@ -2611,9 +2633,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
26112633
await expect(page.locator("#statusLog")).toHaveValue(/OK Palette target set to Stroke\./);
26122634
const shapeOneStyleAfterStrokeMode = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
26132635
expect(shapeOneStyleAfterStrokeMode).toEqual(shapeOneStyleBeforeStrokeMode);
2636+
await page.locator("[data-palette-color='#6fd3ff']").click();
2637+
await expect(page.locator("#statusLog")).toHaveValue(/OK Selected stroke color #6fd3ff from cyan\./);
2638+
const shapeOneStyleAfterStrokeSwatch = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2639+
expect(shapeOneStyleAfterStrokeSwatch).toEqual(shapeOneStyleBeforeStrokeMode);
26142640
await clickPreviewShape(1);
2615-
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"stroke": "#ffffff"');
2616-
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied palette color #ffffff from white to shape row 1 by render surface click\. Target: stroke width 2, opacity 1\./);
2641+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"stroke": "#6fd3ff"');
2642+
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied palette color #6fd3ff from cyan to shape row 1 by render surface click\. Target: stroke width 2, opacity 1\./);
26172643
await page.locator("#objectVectorStudioV2StrokeOpacity").fill("256");
26182644
await page.locator("#objectVectorStudioV2StrokeOpacity").dispatchEvent("change");
26192645
await expect(page.locator("#objectVectorStudioV2StrokeOpacity")).toHaveAttribute("aria-invalid", "true");
@@ -2637,7 +2663,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
26372663
await page.locator("[data-palette-color='#ffffff']").click();
26382664
await page.keyboard.press("I");
26392665
await clickPreviewShape(1);
2640-
await expect(page.locator("#statusLog")).toHaveValue(/OK Sampled stroke color #ffffff from shape row 1\./);
2666+
await expect(page.locator("#statusLog")).toHaveValue(/OK Sampled stroke color #6fd3ff from shape row 1\./);
26412667
await page.keyboard.press("X");
26422668
await expect(page.locator("#statusLog")).toHaveValue(/OK Swapped fill and stroke colors/);
26432669
await page.keyboard.press("D");
@@ -5226,7 +5252,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
52265252
expect(eventMessages).toContain("Object Vector runtime cache miss for ship; cached resolved object object.asteroids.ship.");
52275253
expect(eventMessages).toContain("Object Vector runtime cache miss for ufoSmall; cached resolved object object.asteroids.small-ufo.");
52285254
expect(eventMessages).toContain("Object Vector runtime frame resolved: object.asteroids.ship idle/frame-1.");
5229-
expect(eventMessages).toContain("Object Vector runtime rendered object.asteroids.ship: 1 shapes state=idle frame=frame-1.");
5255+
expect(eventMessages).toContain("Object Vector runtime rendered object.asteroids.ship: 3 shapes state=idle frame=frame-1.");
52305256
expect(eventMessages).toContain("Object Vector runtime rendered object.asteroids.small-ufo: 2 shapes state=active frame=frame-1.");
52315257
expect(eventMessages).not.toContain("matched multiple objects by tags");
52325258
expect(pageErrors).toEqual([]);
@@ -8554,7 +8580,18 @@ test.describe("Workspace Manager V2 bootstrap", () => {
85548580
await page.locator("#objectVectorStudioV2MoveShapeButton").click();
85558581
});
85568582
await expectObjectVectorDirtyAfter("palette color edit", async () => {
8557-
await page.locator("#objectVectorStudioV2PaletteSummary [data-palette-color]").first().click();
8583+
await page.locator("#objectVectorStudioV2PaintModeButton").click();
8584+
const colorToApply = await page.evaluate(() => {
8585+
const app = window.__objectVectorStudioV2App;
8586+
const selectedFill = app.selectedShape()?.style?.fill || "";
8587+
return app.runtimePalette.swatches
8588+
.map((swatch) => swatch.value || swatch.hex || swatch.color || "")
8589+
.find((color) => color && color !== selectedFill);
8590+
});
8591+
expect(colorToApply).toBeTruthy();
8592+
await page.locator(`#objectVectorStudioV2PaletteSummary [data-palette-color="${colorToApply}"]`).click();
8593+
const selectedShapeIndex = await page.evaluate(() => window.__objectVectorStudioV2App.selectedShapeIndex);
8594+
await page.locator(`#objectVectorStudioV2RenderSurface [data-shape-index="${selectedShapeIndex}"]`).click();
85588595
});
85598596
await expectObjectVectorDirtyAfter("shape add edit", async () => {
85608597
await page.locator("[data-shape-tool='rectangle']").click();

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
264264
<button type="button" data-palette-sort="sat" aria-pressed="false">Sat</button>
265265
<button type="button" data-palette-sort="bri" aria-pressed="false">Bri</button>
266266
<button type="button" data-palette-sort="name" aria-pressed="true">Name</button>
267+
<button type="button" data-palette-sort="tag" aria-pressed="false">Tag</button>
267268
</div>
268269
</div>
269270
</section>

0 commit comments

Comments
 (0)