Skip to content

Commit 1ad83b4

Browse files
committed
Restore Colors swatch selection pinning sorting and tags - PR_26159_025-colors-swatch-selection-restore
1 parent 7923226 commit 1ad83b4

4 files changed

Lines changed: 344 additions & 19 deletions

File tree

assets/theme-v2/css/forms.css

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -216,24 +216,41 @@ input[type="color"][data-palette-swatch-size="large"] {
216216
.palette-generator-preview {
217217
display: flex;
218218
flex-direction: column;
219-
gap: var(--space-3);
219+
gap: var(--space-0);
220220
width: 100%;
221221
min-height: calc(var(--space-78) * 4)
222222
}
223223

224224
.palette-generator-preview-row {
225225
display: flex;
226226
flex: 1 1 0;
227-
gap: var(--space-3);
227+
gap: var(--space-0);
228228
min-height: var(--space-0);
229229
width: 100%
230230
}
231231

232232
.palette-generator-preview-swatch {
233+
appearance: none;
234+
box-sizing: border-box;
235+
position: relative;
233236
display: flex;
234237
flex: 1 1 0;
238+
align-items: stretch;
239+
justify-content: stretch;
240+
margin: var(--space-0);
235241
min-width: var(--space-0);
236-
min-height: var(--space-0)
242+
min-height: var(--space-0);
243+
padding: var(--space-0);
244+
border: 0;
245+
border-radius: var(--space-0);
246+
background: var(--transparent);
247+
overflow: hidden;
248+
cursor: pointer
249+
}
250+
251+
.palette-generator-preview-swatch:disabled {
252+
cursor: not-allowed;
253+
opacity: 0.5
237254
}
238255

239256
.palette-generator-preview-swatch input[type="color"] {
@@ -244,7 +261,7 @@ input[type="color"][data-palette-swatch-size="large"] {
244261
min-height: var(--space-0);
245262
padding: var(--space-0);
246263
border: var(--border-width-sm) solid var(--line);
247-
border-radius: var(--radius-xs);
264+
border-radius: var(--space-0);
248265
background: var(--transparent);
249266
overflow: hidden;
250267
pointer-events: none
@@ -256,27 +273,31 @@ input[type="color"][data-palette-swatch-size="large"] {
256273

257274
.palette-generator-preview-swatch input[type="color"]::-webkit-color-swatch {
258275
border: var(--border-width-sm) solid var(--line);
259-
border-radius: var(--radius-xs)
276+
border-radius: var(--space-0)
260277
}
261278

262279
.palette-generator-preview-swatch input[type="color"]::-moz-color-swatch {
263280
border: var(--border-width-sm) solid var(--line);
264-
border-radius: var(--radius-xs)
281+
border-radius: var(--space-0)
265282
}
266283

267284
.palette-swatch-tile:hover,
268-
.palette-swatch-tile:focus-visible {
285+
.palette-swatch-tile:focus-visible,
286+
.palette-generator-preview-swatch:hover,
287+
.palette-generator-preview-swatch:focus-visible {
269288
box-shadow: 0 0 0 var(--space-3) var(--gold-soft), 0 0 var(--space-18) var(--orange-shadow-color);
270289
transform: translateY(-1px);
271290
z-index: var(--z-index-sm)
272291
}
273292

274-
.palette-swatch-tile:focus-visible {
293+
.palette-swatch-tile:focus-visible,
294+
.palette-generator-preview-swatch:focus-visible {
275295
outline: var(--border-width-sm) solid var(--gold);
276296
outline-offset: var(--space-3)
277297
}
278298

279-
.palette-swatch-tile[data-palette-selected="true"] {
299+
.palette-swatch-tile[data-palette-selected="true"],
300+
.palette-generator-preview-swatch[data-palette-selected="true"] {
280301
outline: var(--border-width-sm) solid var(--gold);
281302
outline-offset: var(--space-3);
282303
box-shadow: 0 0 0 var(--space-3) var(--gold-soft), 0 0 var(--space-18) var(--orange-shadow-color);
@@ -297,7 +318,8 @@ input[type="color"][data-palette-swatch-size="large"] {
297318
pointer-events: auto
298319
}
299320

300-
.palette-swatch-tile[data-palette-pinned="true"] .palette-swatch-pin {
321+
.palette-swatch-tile[data-palette-pinned="true"] .palette-swatch-pin,
322+
.palette-generator-preview-swatch[data-palette-pinned="true"] .palette-swatch-pin {
301323
background: var(--green)
302324
}
303325

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# PR_26159_025 Colors Swatch Selection Restore Report
2+
3+
## Summary
4+
5+
Restored generated Colors grid swatch selection, visible pinned state, tag filtering, sorting coverage, and explicit Generate logging while preserving the PR_26159_024 control ownership layout and toolbox visibility behavior.
6+
7+
Playwright impacted: Yes.
8+
9+
## Changed Files
10+
11+
- `assets/theme-v2/css/forms.css`
12+
- `toolbox/colors/colors.js`
13+
- `tests/playwright/tools/PaletteToolMockRepository.spec.mjs`
14+
15+
## Requirement Checklist
16+
17+
| Requirement | Status | Evidence |
18+
| --- | --- | --- |
19+
| Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` first | PASS | Read before implementation. |
20+
| Use PR_26159_024 as base context | PASS | Work preserved the PR_024 curated selector layout and toolbox registration behavior. |
21+
| Grid swatch click adds swatch to Selected Swatches | PASS | `toolbox/colors/colors.js` adds generated swatch click handling through `repository.pinSourceSwatch`; Playwright test `Palette Tool generated grid swatches can be selected, pinned, and refreshed` verifies count and row attributes. |
22+
| Selected swatches remain visible | PASS | Same Playwright test verifies selected generated swatch remains visible before and after control changes and Generate. |
23+
| Duplicate behavior matches prior tool behavior | PASS | Generated selection uses existing repository `pinSourceSwatch` behavior, preserving the repository duplicate check path instead of adding duplicate-specific browser logic. |
24+
| Restore Pin Mode | PASS | Generated grid buttons show pin indicators and `data-palette-pinned`; Playwright verifies pinned swatch persistence after Theme Collection, Palette Type, Variant, Colors, Steps, Contrast, Saturation, and Hue Shift changes. |
25+
| Pin state visible and actionable | PASS | Generated swatches are actionable buttons with `aria-pressed`, title text, selected/pinned data attributes, and the existing pin indicator. |
26+
| Remove spacing between generated grid cells | PASS | `assets/theme-v2/css/forms.css` sets preview gaps, row gaps, swatch padding, margin, and radius to Theme V2 zero tokens; Playwright verifies computed row/column gaps and adjacent cell spacing. |
27+
| Restore sorting controls for Hue, Sat, Brit, Name, Tag in both directions | PASS | Existing sort controls are covered by new `expectSortToggle` assertions in `PaletteToolMockRepository.spec.mjs`. |
28+
| Restore tag checkbox behavior | PASS | `renderTags` now renders tag checkboxes; selected tag filters affect visible Selected Swatches. Playwright verifies checked tags filter rows visibly. |
29+
| Generate explicitly refreshes from current settings and logs status | PASS | Generate now writes `Generated palette grid: <collection> / <type> / <variant>, <colors> colors x <steps> steps.` to status/log; Playwright verifies log output. |
30+
| Preserve Defined Swatch Selector ownership | PASS | No control ownership moved; Theme Collection, Palette Type, Variant remain in Defined Swatch Selector. |
31+
| Preserve Swatch Type / Theme ownership | PASS | Colors, Steps, Contrast, Saturation, Hue Shift, Generate, Reset remain in Swatch Type / Theme. |
32+
| Admin toolbox list remains unfiltered | PASS | `ToolboxRoutePages.spec.mjs` passed and verifies Admin sees registered list tools including Colors. |
33+
| Non-admin filtering remains unchanged | PASS | `ToolboxRoutePages.spec.mjs` passed and verifies creator filtering remains active. |
34+
| Colors remains visible on `toolbox/index.html` | PASS | `ToolboxRoutePages.spec.mjs` passed and verifies the Colors toolbox card/link. |
35+
| No console errors | PASS | Colors and toolbox Playwright lanes use page failure capture; both passed. |
36+
37+
## Validation
38+
39+
| Lane | Result | Evidence |
40+
| --- | --- | --- |
41+
| Changed JS syntax | PASS | `node --check toolbox/colors/colors.js`; `node --check tests/playwright/tools/PaletteToolMockRepository.spec.mjs`. |
42+
| Changed-file/static validation | PASS | `git diff --check`. |
43+
| Targeted Colors runtime/UI lane | PASS | `npx playwright test tests/playwright/tools/PaletteToolMockRepository.spec.mjs --timeout=180000` passed 6/6. |
44+
| Toolbox/page validation | PASS | `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --timeout=180000` passed 2/2. |
45+
46+
## Skipped Lanes
47+
48+
- Full samples validation: skipped by request; this PR only touched the Colors tool UI/runtime, reusable Theme V2 form styling for palette swatches, and targeted Playwright tests.
49+
50+
## Notes
51+
52+
- The generated grid selection path intentionally reuses the existing repository pin/add path so prior duplicate and selected-swatch behavior stays centralized.
53+
- During validation, the new no-spacing assertion caught an accidental selected-state comparison for unpinned generated swatches. The selection predicate now only marks generated swatches selected when a pinned swatch exists and matches the selected project swatch.

tests/playwright/tools/PaletteToolMockRepository.spec.mjs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,23 @@ async function currentPreviewHexes(page, row) {
148148
));
149149
}
150150

151+
async function visiblePaletteNames(page) {
152+
return page.locator("[data-palette-user-list] [data-palette-swatch-row]").evaluateAll((swatches) => (
153+
swatches.map((swatch) => swatch.dataset.paletteSwatchName)
154+
));
155+
}
156+
157+
async function expectSortToggle(page, key, label) {
158+
const button = page.locator(`[data-palette-user-sort] [data-palette-sort-key='${key}']`);
159+
await button.click();
160+
await expect(button).toHaveText(`${label} ^`);
161+
const ascending = await visiblePaletteNames(page);
162+
await button.click();
163+
await expect(button).toHaveText(`${label} v`);
164+
const descending = await visiblePaletteNames(page);
165+
expect(ascending).toEqual([...descending].reverse());
166+
}
167+
151168
test("Palette repository owns project swatches and protects invalid payloads", async () => {
152169
const repository = createProjectWorkspacePaletteRepository({ sourcePaletteRows });
153170
const baseline = repository.getSnapshot();
@@ -398,6 +415,73 @@ test("Palette Tool renders curated swatch selector controls and live preview", a
398415

399416
await page.locator("[data-palette-generator-generate]").click();
400417
await expect(page.locator("[data-palette-generator-preview-status]")).toContainText("Computer / Amiga");
418+
await expect(page.locator("[data-palette-log]")).toContainText("Generated palette grid: Computer / Amiga");
419+
420+
expectNoPageFailures(failures);
421+
} finally {
422+
await workspaceV2CoverageReporter.stop(page);
423+
await failures.server.close();
424+
}
425+
});
426+
427+
test("Palette Tool generated grid swatches can be selected, pinned, and refreshed", async ({ page }) => {
428+
test.setTimeout(120000);
429+
const failures = await openRepoPage(page, "/toolbox/colors/index.html");
430+
431+
try {
432+
const firstGenerated = page.locator("[data-palette-generator-swatch]").first();
433+
await expect(firstGenerated).toHaveAttribute("data-palette-pinned", "false");
434+
const generatedName = await firstGenerated.getAttribute("data-palette-generator-name");
435+
const generatedSymbol = await firstGenerated.getAttribute("data-palette-generator-symbol");
436+
const generatedHex = await firstGenerated.getAttribute("data-palette-generator-hex");
437+
438+
const gridSpacing = await page.locator("[data-palette-generator-preview]").evaluate((preview) => {
439+
const firstRow = preview.querySelector("[data-palette-generator-preview-row]");
440+
const cells = Array.from(firstRow.querySelectorAll("[data-palette-generator-swatch]")).slice(0, 2);
441+
const leftBox = cells[0].getBoundingClientRect();
442+
const rightBox = cells[1].getBoundingClientRect();
443+
return {
444+
columnGap: getComputedStyle(firstRow).columnGap,
445+
previewGap: getComputedStyle(preview).rowGap,
446+
xGap: Math.round(rightBox.left - leftBox.right)
447+
};
448+
});
449+
expect(gridSpacing.previewGap).toBe("0px");
450+
expect(gridSpacing.columnGap).toBe("0px");
451+
expect(Math.abs(gridSpacing.xGap)).toBeLessThanOrEqual(1);
452+
453+
await firstGenerated.click();
454+
await expect(page.locator("[data-palette-count]")).toHaveText("1");
455+
await expect(page.locator(`[data-palette-swatch-row='${generatedSymbol}']`)).toHaveAttribute("data-palette-swatch-name", generatedName || "");
456+
await expect(page.locator(`[data-palette-swatch-row='${generatedSymbol}']`)).toHaveAttribute("data-palette-swatch-hex", generatedHex || "");
457+
await expect(page.locator(`[data-palette-swatch-row='${generatedSymbol}']`)).toHaveAttribute("data-palette-selected", "true");
458+
await expect(page.locator("[data-palette-generator-swatch]").first()).toHaveAttribute("data-palette-pinned", "true");
459+
await expect(page.locator("[data-palette-generator-swatch]").first().locator("[data-palette-pin-indicator]")).toHaveCount(1);
460+
await expect(page.locator("[data-palette-log]")).toContainText("Pinned generated swatch");
461+
462+
await page.locator("[data-palette-theme-collection]").selectOption("Sci-Fi");
463+
await page.locator("[data-palette-generator-type]").selectOption("Cyberpunk");
464+
await page.locator("[data-palette-generator-variant]").selectOption("High Contrast");
465+
await page.locator("[data-palette-generator-colors]").selectOption("12");
466+
await page.locator("[data-palette-generator-steps]").selectOption("16");
467+
await page.locator("[data-palette-generator-contrast]").evaluate((control) => {
468+
control.value = "72";
469+
control.dispatchEvent(new Event("input", { bubbles: true }));
470+
});
471+
await page.locator("[data-palette-generator-saturation]").evaluate((control) => {
472+
control.value = "58";
473+
control.dispatchEvent(new Event("input", { bubbles: true }));
474+
});
475+
await page.locator("[data-palette-generator-hue-shift]").evaluate((control) => {
476+
control.value = "-35";
477+
control.dispatchEvent(new Event("input", { bubbles: true }));
478+
});
479+
await expect(page.locator(`[data-palette-swatch-row='${generatedSymbol}']`)).toHaveAttribute("data-palette-swatch-name", generatedName || "");
480+
await expect(page.locator("[data-palette-count]")).toHaveText("1");
481+
482+
await page.locator("[data-palette-generator-generate]").click();
483+
await expect(page.locator("[data-palette-log]")).toContainText("Generated palette grid: Sci-Fi / Cyberpunk / High Contrast, 12 colors x 16 steps.");
484+
await expect(page.locator(`[data-palette-swatch-row='${generatedSymbol}']`)).toHaveAttribute("data-palette-swatch-name", generatedName || "");
401485

402486
expectNoPageFailures(failures);
403487
} finally {
@@ -468,6 +552,28 @@ test("Palette Tool batch tags checked project palette swatches", async ({ page }
468552
await expect(brownTile).toHaveAttribute("data-palette-swatch-tags", "batch");
469553
await expect(blueTile).toHaveAttribute("data-palette-swatch-tags", "");
470554

555+
await expectSortToggle(page, "hue", "Hue");
556+
await expectSortToggle(page, "saturation", "Sat");
557+
await expectSortToggle(page, "brightness", "Brit");
558+
await expectSortToggle(page, "name", "Name");
559+
await expectSortToggle(page, "tag", "Tag");
560+
561+
const tagFilters = page.locator("[data-palette-tag-filter]");
562+
const tagFilterItems = page.locator("[data-palette-tags-list] li");
563+
await expect(tagFilters).toHaveCount(3);
564+
await expect(tagFilterItems).toHaveText(["afterclear", "batch", "solo"]);
565+
await page.locator("[data-palette-tag-filter='batch']").check();
566+
await expect(page.locator("[data-palette-user-list] [data-palette-swatch-name='Anchor']")).toHaveCount(1);
567+
await expect(page.locator("[data-palette-user-list] [data-palette-swatch-name='Brownstone']")).toHaveCount(1);
568+
await expect(page.locator("[data-palette-user-list] [data-palette-swatch-name='Bluegate']")).toHaveCount(0);
569+
await page.locator("[data-palette-tag-filter='solo']").check();
570+
await expect(page.locator("[data-palette-user-list] [data-palette-swatch-name='Anchor']")).toHaveCount(1);
571+
await expect(page.locator("[data-palette-user-list] [data-palette-swatch-name='Brownstone']")).toHaveCount(0);
572+
await expect(page.locator("[data-palette-user-list] [data-palette-swatch-name='Bluegate']")).toHaveCount(0);
573+
await page.locator("[data-palette-tag-filter='batch']").uncheck();
574+
await page.locator("[data-palette-tag-filter='solo']").uncheck();
575+
await expect(page.locator("[data-palette-user-list] [data-palette-swatch-row]")).toHaveCount(3);
576+
471577
expectNoPageFailures(failures);
472578
} finally {
473579
await workspaceV2CoverageReporter.stop(page);

0 commit comments

Comments
 (0)