Sitesmith design payload: brand colours carry their published slot (G1)
buildDesignPayload numbered brand colours by list position while the page
publishes them as --wsb-brand-<slot>, so the prompt could name a different
colour than the page renders. It now uses publishedBrandSwatches (same rule as
the publisher) and sends {name?, hex, slot}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3413c7816d
commit
7a58921ba3
2 files changed
+27
-23
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { buildDesignPayload } from './sitesmith-design';
|
||||
import { brandTokenDecls } from './swatches';
|
||||
import { DEFAULT_SITE_DESIGN } from '../state/SiteDesignContext';
|
||||
import { normalizeUrlLabel, validateAttachment, sniffImageType, formatBytes } from './sitesmith-attachments';
|
||||
|
||||
@@ -14,18 +15,19 @@ describe('buildDesignPayload', () => {
|
||||
expect(Object.keys(p)).not.toContain('favicon');
|
||||
expect(p.brandColors).toBeUndefined();
|
||||
});
|
||||
test('brand swatches become brandColors (max 6, valid hex only, optional)', () => {
|
||||
test('brand swatches become brandColors with their PUBLISHED slot (exactly the --wsb-brand-<slot> the page renders)', () => {
|
||||
const swatches = [
|
||||
{ label: 'Ink', hex: '#0F172A', brand: true }, { label: 'Not brand', hex: '#ffffff', brand: false },
|
||||
{ label: 'Bad', hex: 'red', brand: true }, { label: 'A<b>', hex: '#abc', brand: true },
|
||||
...Array.from({ length: 8 }, (_, i) => ({ label: `S${i}`, hex: '#00000' + i, brand: true })),
|
||||
{ id: 'a', label: 'Ink', hex: '#0F172A', brand: true, slot: 3 }, { id: 'b', label: 'Not brand', hex: '#ffffff', brand: false },
|
||||
{ id: 'c', label: 'Bad', hex: 'red', brand: true, slot: 2 }, { id: 'd', label: 'A<b>', hex: '#abc', brand: true, slot: 1 },
|
||||
{ id: 'e', label: 'No slot', hex: '#123456', brand: true }, { id: 'f', label: 'Far', hex: '#654321', brand: true, slot: 7 },
|
||||
{ id: 'g', label: 'Dup', hex: '#111111', brand: true, slot: 3 },
|
||||
];
|
||||
const p = buildDesignPayload({ ...DEFAULT_SITE_DESIGN, swatches } as any);
|
||||
const b = p.brandColors as Array<{ name?: string; hex: string }>;
|
||||
expect(b).toHaveLength(6);
|
||||
expect(b[0]).toEqual({ name: 'Ink', hex: '#0f172a' });
|
||||
expect(b[1].name).toBe('Ab');
|
||||
expect(b.every((x) => /^#[0-9a-f]{3,8}$/.test(x.hex))).toBe(true);
|
||||
const design = { ...DEFAULT_SITE_DESIGN, swatches } as any;
|
||||
const p = buildDesignPayload(design);
|
||||
const b = p.brandColors as Array<{ name?: string; hex: string; slot: number }>;
|
||||
// slot order, only brand + slot 1..6 + valid hex, first per slot wins -- same rule as publishedBrandSwatches
|
||||
expect(b).toEqual([{ name: 'Ab', hex: '#abc', slot: 1 }, { name: 'Ink', hex: '#0f172a', slot: 3 }]);
|
||||
expect(b.map((x) => `;--wsb-brand-${x.slot}:${x.hex}`).join('')).toBe(brandTokenDecls(swatches).toLowerCase());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
/**
|
||||
* The site design block sent with every Sitesmith request, in the shape the backend prompt builder
|
||||
* (sitesmith_design_block in libs/sitesmith-prompts.php) reads: SiteDesign-named keys plus
|
||||
* `brandColors: [{name?, hex}]` (up to 6, from the colour-system swatches flagged `brand`).
|
||||
* `brandColors: [{name?, hex, slot}]` (the published brand swatches, slot 1..6 = --wsb-brand-<slot>).
|
||||
* The backend re-validates every value; this only keeps obviously unusable values out.
|
||||
*/
|
||||
import type { SiteDesign } from '../state/SiteDesignContext';
|
||||
import { publishedBrandSwatches } from './swatches';
|
||||
|
||||
export interface DesignSwatch { label?: string; hex?: string; brand?: boolean }
|
||||
export interface DesignSwatch { label?: string; hex?: string; brand?: boolean; slot?: number }
|
||||
|
||||
const KEYS = [
|
||||
'primaryColor', 'secondaryColor', 'accentColor', 'backgroundColor', 'textColor', 'mutedTextColor',
|
||||
'borderColor', 'headingFont', 'bodyFont', 'borderRadius', 'buttonRadius',
|
||||
] as const;
|
||||
|
||||
const HEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
||||
|
||||
export function buildDesignPayload(design: SiteDesign & { swatches?: DesignSwatch[] }): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = {};
|
||||
@@ -23,17 +23,19 @@ export function buildDesignPayload(design: SiteDesign & { swatches?: DesignSwatc
|
||||
// Font stacks: the backend accepts letters, digits, space, comma, dash and underscore only.
|
||||
out[k] = k.endsWith('Font') ? v.replace(/["']/g, '').trim() : v.trim();
|
||||
}
|
||||
// Brand colours go out with the SLOT they are published under (--wsb-brand-<slot>, see
|
||||
// utils/swatches.ts publishedBrandSwatches): the prompt must name the same token the page renders,
|
||||
// so list position is never used. Same selection rule as the publisher; label = that swatch's label.
|
||||
const sw = (design as { swatches?: unknown }).swatches;
|
||||
if (Array.isArray(sw)) {
|
||||
const brand = sw
|
||||
.filter((s): s is DesignSwatch => !!s && typeof s === 'object' && (s as DesignSwatch).brand === true
|
||||
&& typeof (s as DesignSwatch).hex === 'string' && HEX.test((s as DesignSwatch).hex!.trim()))
|
||||
.slice(0, 6)
|
||||
.map((s) => {
|
||||
const name = typeof s.label === 'string' ? s.label.replace(/[\u0000-\u001f<>]/g, '').trim().slice(0, 40) : '';
|
||||
return name ? { name, hex: s.hex!.trim().toLowerCase() } : { hex: s.hex!.trim().toLowerCase() };
|
||||
});
|
||||
if (brand.length) out.brandColors = brand;
|
||||
const published = publishedBrandSwatches(sw);
|
||||
if (published.length && Array.isArray(sw)) {
|
||||
const brand = published.map(({ slot, hex }) => {
|
||||
const src = (sw as DesignSwatch[]).find((s) => !!s && s.brand === true && s.slot === slot && typeof s.hex === 'string' && s.hex.trim() === hex);
|
||||
const name = typeof src?.label === 'string' ? src.label.replace(/[\u0000-\u001f<>]/g, '').trim().slice(0, 40) : '';
|
||||
const h = hex.trim().toLowerCase();
|
||||
return name ? { name, hex: h, slot } : { hex: h, slot };
|
||||
});
|
||||
out.brandColors = brand;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in new issue
Block a user