Skip to content

Commit a12f187

Browse files
committed
Merge branch 'staging'
2 parents 63dcb60 + 4b038f1 commit a12f187

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'helixir': patch
3+
---
4+
5+
Add component-specific "common mistakes" section to narrative output with valid part names, no-CSS-API warnings, React event guidance, and styling_preflight reminder

packages/core/src/handlers/narrative.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,51 @@ export function buildNarrative(meta: ComponentMetadata): string {
167167
sections.push(eventLines.join('\n'));
168168
}
169169

170+
// --- Common mistakes (component-specific) ---
171+
const mistakes: string[] = [];
172+
173+
if (meta.cssParts.length > 0) {
174+
const partNames = meta.cssParts.map((p) => `\`${p.name}\``).join(', ');
175+
mistakes.push(
176+
`Only these CSS parts exist: ${partNames}. ` +
177+
`Any other part name in \`::part()\` will silently fail.`,
178+
);
179+
}
180+
181+
if (meta.cssProperties.length > 0 && meta.cssParts.length === 0) {
182+
mistakes.push(
183+
`This component has NO CSS parts — use CSS custom properties only. ` +
184+
`\`::part()\` selectors will not match anything.`,
185+
);
186+
}
187+
188+
if (meta.cssProperties.length === 0 && meta.cssParts.length === 0) {
189+
mistakes.push(
190+
`This component exposes NO CSS API (no parts, no custom properties). ` +
191+
`You cannot style its internals. Only the host element box can be styled.`,
192+
);
193+
}
194+
195+
if (meta.events.some((e) => e.name.includes('-'))) {
196+
mistakes.push(
197+
`Custom events (with hyphens) cannot use React \`onXxx\` props. ` +
198+
`Use \`ref.current.addEventListener()\` instead.`,
199+
);
200+
}
201+
202+
if (meta.slots.length > 0 && meta.cssParts.length > 0) {
203+
mistakes.push(
204+
`Style slotted content in light DOM CSS (before it enters the slot), ` +
205+
`not with \`::slotted()\` in your consumer stylesheet.`,
206+
);
207+
}
208+
209+
if (mistakes.length > 0) {
210+
const mistakeLines = ['**Common mistakes:**', ...mistakes.map((m) => `- ${m}`)];
211+
mistakeLines.push('', `*Run \`styling_preflight\` with your CSS to validate all references.*`);
212+
sections.push(mistakeLines.join('\n'));
213+
}
214+
170215
return sections.join('\n\n');
171216
}
172217

tests/handlers/narrative.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,65 @@ describe('buildNarrative — tailored Shadow DOM constraints', () => {
138138
expect(result).toContain(':host');
139139
});
140140
});
141+
142+
// ---------------------------------------------------------------------------
143+
// buildNarrative — common mistakes section
144+
// ---------------------------------------------------------------------------
145+
146+
describe('buildNarrative — common mistakes', () => {
147+
it('lists valid part names when component has parts', () => {
148+
const result = getComponentNarrative('my-button', FIXTURE_CEM);
149+
expect(result).toContain('Common mistakes');
150+
expect(result).toContain('`base`');
151+
expect(result).toContain('silently fail');
152+
});
153+
154+
it('warns about no CSS parts when component has only properties', () => {
155+
const meta: ComponentMetadata = {
156+
tagName: 'my-test',
157+
description: 'Test',
158+
members: [],
159+
events: [],
160+
slots: [],
161+
cssParts: [],
162+
cssProperties: [{ name: '--test-color', description: '' }],
163+
};
164+
const result = buildNarrative(meta);
165+
expect(result).toContain('NO CSS parts');
166+
expect(result).toContain('::part()');
167+
});
168+
169+
it('warns about no CSS API when component has neither', () => {
170+
const meta: ComponentMetadata = {
171+
tagName: 'my-bare',
172+
description: 'Bare',
173+
members: [],
174+
events: [],
175+
slots: [],
176+
cssParts: [],
177+
cssProperties: [],
178+
};
179+
const result = buildNarrative(meta);
180+
expect(result).toContain('NO CSS API');
181+
});
182+
183+
it('warns about custom events and React onXxx props', () => {
184+
const meta: ComponentMetadata = {
185+
tagName: 'my-dialog',
186+
description: 'Dialog',
187+
members: [],
188+
events: [{ name: 'my-close', type: 'CustomEvent', description: '' }],
189+
slots: [],
190+
cssParts: [],
191+
cssProperties: [],
192+
};
193+
const result = buildNarrative(meta);
194+
expect(result).toContain('React');
195+
expect(result).toContain('addEventListener');
196+
});
197+
198+
it('includes styling_preflight reminder', () => {
199+
const result = getComponentNarrative('my-button', FIXTURE_CEM);
200+
expect(result).toContain('styling_preflight');
201+
});
202+
});

0 commit comments

Comments
 (0)