fix(pen-core): nav fill inject preserves gradient / image fills
Previous `hasSolidFill` only matched `type === 'solid'`. Sub-agents legitimately put `linear_gradient` (sunrise hero, accent ribbon), `radial_gradient` (splash entries), or `image` (branded photo banners) on top app bars and other nav surfaces, and `hasSolidFill` would return false for those — making the inject pass overwrite the gradient/image with a flat `$color-surface` solid. Renamed to `hasAnyFill`; matches any first-entry shape with a recognized `type` field. Sub-agent intent (any non-empty fill) now short-circuits the inject. Three new tests cover linear gradient, radial gradient, and image fills explicitly — all preserved.
This commit is contained in:
parent
4212220d57
commit
41f49c66e5
|
|
@ -50,6 +50,82 @@ describe('injectMissingNavSurfaceFill', () => {
|
|||
expect((navbar as PenNode & { fill?: unknown }).fill).toEqual(solidFill('#0F172A'));
|
||||
});
|
||||
|
||||
it('preserves a linear-gradient nav fill (does not overwrite with solid)', () => {
|
||||
const gradientFill = [
|
||||
{
|
||||
type: 'linear_gradient' as const,
|
||||
stops: [
|
||||
{ offset: 0, color: '#F97316' },
|
||||
{ offset: 1, color: '#EA580C' },
|
||||
],
|
||||
angle: 90,
|
||||
},
|
||||
];
|
||||
const navbar = frame({
|
||||
id: 'top-bar',
|
||||
role: 'top-app-bar',
|
||||
fill: gradientFill as never,
|
||||
children: [],
|
||||
});
|
||||
const root = frame({
|
||||
id: 'root',
|
||||
fill: solidFill('#FFFFFF'),
|
||||
children: [navbar],
|
||||
});
|
||||
const changed = injectMissingNavSurfaceFill(root);
|
||||
expect(changed).toBe(false);
|
||||
expect((navbar as PenNode & { fill?: unknown }).fill).toEqual(gradientFill);
|
||||
});
|
||||
|
||||
it('preserves a radial-gradient nav fill', () => {
|
||||
const radial = [
|
||||
{
|
||||
type: 'radial_gradient' as const,
|
||||
stops: [
|
||||
{ offset: 0, color: '#FFFFFF' },
|
||||
{ offset: 1, color: '#000000' },
|
||||
],
|
||||
center: { x: 0.5, y: 0.5 },
|
||||
},
|
||||
];
|
||||
const navbar = frame({
|
||||
id: 'splash-nav',
|
||||
role: 'navbar',
|
||||
fill: radial as never,
|
||||
children: [],
|
||||
});
|
||||
const root = frame({
|
||||
id: 'root',
|
||||
fill: solidFill('#FFF8F0'),
|
||||
children: [navbar],
|
||||
});
|
||||
injectMissingNavSurfaceFill(root);
|
||||
expect((navbar as PenNode & { fill?: unknown }).fill).toEqual(radial);
|
||||
});
|
||||
|
||||
it('preserves an image nav fill (branded nav surface with photo)', () => {
|
||||
const imageFill = [
|
||||
{
|
||||
type: 'image' as const,
|
||||
src: 'https://example.com/banner.jpg',
|
||||
scaleMode: 'cover',
|
||||
},
|
||||
];
|
||||
const navbar = frame({
|
||||
id: 'hero-nav',
|
||||
role: 'top-app-bar',
|
||||
fill: imageFill as never,
|
||||
children: [],
|
||||
});
|
||||
const root = frame({
|
||||
id: 'root',
|
||||
fill: solidFill('#FFFFFF'),
|
||||
children: [navbar],
|
||||
});
|
||||
injectMissingNavSurfaceFill(root);
|
||||
expect((navbar as PenNode & { fill?: unknown }).fill).toEqual(imageFill);
|
||||
});
|
||||
|
||||
it('does not touch nav frames nested inside cards/sections', () => {
|
||||
const nestedNav = frame({
|
||||
id: 'inner-nav',
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function injectMissingNavSurfaceFill(rootFrame: PenNode): boolean {
|
|||
const role = (child as PenNode & { role?: string }).role;
|
||||
if (!role || !NAV_ROLES.has(role)) continue;
|
||||
const existing = (child as PenNode & { fill?: PenFill[] | string }).fill;
|
||||
if (hasSolidFill(existing)) continue;
|
||||
if (hasAnyFill(existing)) continue;
|
||||
(child as PenNode & { fill?: PenFill[] }).fill = [
|
||||
{ type: 'solid', color: '$color-surface' } as SolidFill,
|
||||
];
|
||||
|
|
@ -48,10 +48,19 @@ export function injectMissingNavSurfaceFill(rootFrame: PenNode): boolean {
|
|||
return changed;
|
||||
}
|
||||
|
||||
function hasSolidFill(fill: PenFill[] | string | undefined): boolean {
|
||||
/**
|
||||
* True when the frame already carries ANY valid fill the renderer would
|
||||
* paint — solid OR gradient (linear/radial) OR image. We must NOT
|
||||
* overwrite a non-solid fill with our default `$color-surface` solid:
|
||||
* sub-agents legitimately put `linear_gradient` on top app bars
|
||||
* (sunrise hero gradient), `image` on hero / branded nav surfaces, and
|
||||
* `radial_gradient` on splash-style entries. Keep their intent.
|
||||
*/
|
||||
function hasAnyFill(fill: PenFill[] | string | undefined): boolean {
|
||||
if (!fill) return false;
|
||||
if (typeof fill === 'string') return fill.length > 0;
|
||||
if (!Array.isArray(fill) || fill.length === 0) return false;
|
||||
// Any first entry with a recognized `type` counts as intentional fill.
|
||||
const first = fill[0];
|
||||
return first?.type === 'solid' && typeof (first as SolidFill).color === 'string';
|
||||
return !!first && typeof (first as { type?: string }).type === 'string';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue