Skip to content

Commit 3a49420

Browse files
author
DavidQ
committed
Align opacity controls with Paint Stroke mode based application workflow - PR_26133_059-palette-opacity-mode-and-application-flow
1 parent 9690eda commit 3a49420

6 files changed

Lines changed: 43 additions & 41 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_058 Playwright V8 Coverage Report
1+
# PR_26133_059 Playwright V8 Coverage Report
22

3-
Task: PR_26133_058-palette-tag-sort-and-paint-stroke-mode-flow
3+
Task: PR_26133_059-palette-opacity-mode-and-application-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 5688/5688; executed functions 600/631
27+
(95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 5669/5669; 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 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.
38+
The Workspace V2 run exercised Object Vector Studio V2 opacity control layout, active Fill/Stroke opacity selection without immediate shape mutation, shape-click opacity application through the existing Paint/Stroke workflow, schema validation, and Asteroids runtime object-vector rendering.
Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_058 Workspace V2 Playwright Results
1+
# PR_26133_059 Workspace V2 Playwright Results
22

3-
Task: PR_26133_058-palette-tag-sort-and-paint-stroke-mode-flow
3+
Task: PR_26133_059-palette-opacity-mode-and-application-flow
44
Date: 2026-05-15
55

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

1515
## PR-Specific Coverage
1616

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.
17+
- Verified the Palette opacity controls render as `Opacity`, then compact `Fill` and `Stroke` inputs.
18+
- Verified changing Fill opacity updates the active Fill opacity value only and does not immediately mutate the selected shape.
19+
- Verified changing Stroke opacity updates the active Stroke opacity value only and does not immediately mutate the selected shape.
20+
- Verified clicking a shape applies the currently selected Fill/Stroke opacity values through the existing Paint/Stroke application flow.
21+
- Verified Paint/Stroke color mode behavior remains selection-first and shape-click apply.
2222

2323
## Additional Validation
2424

2525
- Focused palette/layout slice passed:
2626
`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.
3127
- `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: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,14 +1740,17 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17401740
const widthInput = content.querySelector("#objectVectorStudioV2StrokeWidth").getBoundingClientRect();
17411741
const fillOpacityLabel = content.querySelector("label[for='objectVectorStudioV2FillOpacity']").getBoundingClientRect();
17421742
const strokeOpacityLabel = content.querySelector("label[for='objectVectorStudioV2StrokeOpacity']").getBoundingClientRect();
1743+
const opacityHeading = content.querySelector(".object-vector-studio-v2__palette-opacity-heading").getBoundingClientRect();
17431744
const opacityInputs = Array.from(opacityRow.querySelectorAll("input"));
17441745
const opacityInputRects = opacityInputs.map((input) => input.getBoundingClientRect());
17451746
return {
17461747
opacityBelowPrimary: fillOpacityLabel.top >= Math.max(paintButton.bottom, strokeButton.bottom, widthLabel.bottom),
1747-
opacityInline: Math.abs((fillOpacityLabel.top + fillOpacityLabel.height / 2) - (strokeOpacityLabel.top + strokeOpacityLabel.height / 2)) < 4,
1748+
opacityHeading: content.querySelector(".object-vector-studio-v2__palette-opacity-heading").textContent.trim(),
1749+
opacityInline: [fillOpacityLabel, strokeOpacityLabel].every((rect) => Math.abs((opacityHeading.top + opacityHeading.height / 2) - (rect.top + rect.height / 2)) < 4),
17481750
opacityInputFitsFourDigits: opacityInputRects.every((rect) => Math.round(rect.width) >= 54),
17491751
opacityInputRanges: opacityInputs.map((input) => ({ max: input.max, min: input.min, step: input.step, value: input.value })),
17501752
opacityLabels: Array.from(opacityRow.querySelectorAll("label > span")).map((label) => label.textContent.trim()),
1753+
opacityOrder: Array.from(opacityRow.children).map((element) => element.textContent.trim().replace(/\s+/g, " ")),
17511754
primaryInline: [strokeButton, widthLabel].every((rect) => Math.abs((paintButton.top + paintButton.height / 2) - (rect.top + rect.height / 2)) < 4),
17521755
primaryOrder: Array.from(primaryRow.children).map((element) => element.textContent.trim()),
17531756
widthInputFitsXxDotX: Math.round(widthInput.width) >= 58,
@@ -1756,13 +1759,15 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17561759
});
17571760
expect(paletteControlLayout).toEqual({
17581761
opacityBelowPrimary: true,
1762+
opacityHeading: "Opacity",
17591763
opacityInline: true,
17601764
opacityInputFitsFourDigits: true,
17611765
opacityInputRanges: [
17621766
{ max: "255", min: "0", step: "1", value: "255" },
17631767
{ max: "255", min: "0", step: "1", value: "255" }
17641768
],
1765-
opacityLabels: ["Fill Op", "Stroke Op"],
1769+
opacityLabels: ["Fill", "Stroke"],
1770+
opacityOrder: ["Opacity", "Fill", "Stroke"],
17661771
primaryInline: true,
17671772
primaryOrder: ["Paint", "Stroke", "Width"],
17681773
widthInputFitsXxDotX: true,
@@ -2625,9 +2630,15 @@ test.describe("Workspace Manager V2 bootstrap", () => {
26252630
await page.locator("#objectVectorStudioV2FillOpacity").dispatchEvent("change");
26262631
await expect(page.locator("#objectVectorStudioV2FillOpacity")).not.toHaveAttribute("aria-invalid", "true");
26272632
await expect(page.locator("#objectVectorStudioV2FillOpacity")).toHaveValue("128");
2633+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).not.toContainText('"fillOpacity": 0.502');
2634+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='1']")).toHaveAttribute("fill-opacity", "1");
2635+
await expect(page.locator("#statusLog")).toHaveValue(/OK Selected fill opacity 0\.502\./);
2636+
const shapeOneStyleAfterFillOpacityInput = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2637+
expect(shapeOneStyleAfterFillOpacityInput.fillOpacity).toBe(1);
2638+
await clickPreviewShape(1);
26282639
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"fillOpacity": 0.502');
26292640
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='1']")).toHaveAttribute("fill-opacity", "0.502");
2630-
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied fill opacity 0\.502 to shape row 1\./);
2641+
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied palette color #6fd3ff from cyan to shape row 1 by render surface click\. Target: paint opacity 0\.502\./);
26312642
const shapeOneStyleBeforeStrokeMode = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
26322643
await page.locator("#objectVectorStudioV2StrokeModeButton").click();
26332644
await expect(page.locator("#statusLog")).toHaveValue(/OK Palette target set to Stroke\./);
@@ -2649,9 +2660,15 @@ test.describe("Workspace Manager V2 bootstrap", () => {
26492660
await page.locator("#objectVectorStudioV2StrokeOpacity").dispatchEvent("change");
26502661
await expect(page.locator("#objectVectorStudioV2StrokeOpacity")).not.toHaveAttribute("aria-invalid", "true");
26512662
await expect(page.locator("#objectVectorStudioV2StrokeOpacity")).toHaveValue("166");
2663+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).not.toContainText('"strokeOpacity": 0.651');
2664+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='1']")).toHaveAttribute("stroke-opacity", "1");
2665+
await expect(page.locator("#statusLog")).toHaveValue(/OK Selected stroke opacity 0\.651\./);
2666+
const shapeOneStyleAfterStrokeOpacityInput = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2667+
expect(shapeOneStyleAfterStrokeOpacityInput.strokeOpacity).toBe(1);
2668+
await clickPreviewShape(1);
26522669
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"strokeOpacity": 0.651');
26532670
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='1']")).toHaveAttribute("stroke-opacity", "0.651");
2654-
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied stroke opacity 0\.651 to shape row 1\./);
2671+
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 0\.651\./);
26552672
await page.evaluate(() => {
26562673
window.__objectVectorStudioV2App.selectedStrokeColor = "#123456";
26572674
window.__objectVectorStudioV2App.selectedStrokeLabel = "manual rogue";

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,13 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
247247
</label>
248248
</div>
249249
<div class="object-vector-studio-v2__palette-opacity-row">
250+
<span class="object-vector-studio-v2__palette-opacity-heading">Opacity</span>
250251
<label class="object-vector-studio-v2__inline-field" for="objectVectorStudioV2FillOpacity">
251-
<span>Fill Op</span>
252+
<span>Fill</span>
252253
<input id="objectVectorStudioV2FillOpacity" type="number" min="0" max="255" step="1" value="255">
253254
</label>
254255
<label class="object-vector-studio-v2__inline-field" for="objectVectorStudioV2StrokeOpacity">
255-
<span>Stroke Op</span>
256+
<span>Stroke</span>
256257
<input id="objectVectorStudioV2StrokeOpacity" type="number" min="0" max="255" step="1" value="255">
257258
</label>
258259
</div>

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

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4199,26 +4199,7 @@ export class ToolStarterApp {
41994199
this.selectedFillOpacity = opacity.value;
42004200
}
42014201
this.updatePaletteModeSwatches();
4202-
if (this.selectedShapeIndex < 0) {
4203-
this.statusLog.write(`OK Palette ${normalizedTarget} opacity set to ${opacity.value}.`);
4204-
return;
4205-
}
4206-
const selected = sortedShapes(this.selectedObject())[this.selectedShapeIndex] || null;
4207-
if (!selected) {
4208-
this.statusLog.write(`OK Palette ${normalizedTarget} opacity set to ${opacity.value}.`);
4209-
return;
4210-
}
4211-
if (selected.locked) {
4212-
this.statusLog.write(`WARN Palette ${normalizedTarget} opacity skipped: shape row ${this.selectedShapeIndex} is locked.`);
4213-
return;
4214-
}
4215-
if (this.guardSelectedObjectMutation("Palette opacity application")) {
4216-
return;
4217-
}
4218-
const nextPayload = this.cloneCurrentPayload();
4219-
const shape = this.findShapeInPayload(nextPayload, this.selectedShapeIndex);
4220-
shape.style[normalizedTarget === "stroke" ? "strokeOpacity" : "fillOpacity"] = opacity.value;
4221-
this.commitPayloadUpdate(nextPayload, this.selectedObjectId, this.selectedShapeIndex, `OK Applied ${normalizedTarget} opacity ${opacity.value} to shape row ${this.selectedShapeIndex}.`, "Palette opacity application failed schema validation");
4202+
this.statusLog.write(`OK Selected ${normalizedTarget} opacity ${opacity.value}.`);
42224203
}
42234204

42244205
selectPaletteColor(color, label) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,13 @@ textarea:hover {
13221322
font-size: 0.72rem;
13231323
}
13241324

1325+
.object-vector-studio-v2__palette-opacity-heading {
1326+
flex: 0 0 auto;
1327+
font-size: 0.74rem;
1328+
font-weight: 700;
1329+
line-height: 1;
1330+
}
1331+
13251332
.object-vector-studio-v2__palette-opacity-row .object-vector-studio-v2__inline-field input {
13261333
width: 54px;
13271334
}

0 commit comments

Comments
 (0)