Skip to content
111 changes: 85 additions & 26 deletions .github/skills/add-component-property/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,43 @@ public propertyName = 0;
public propertyName: ComplexType = defaultValue;
```

### 2. Update Component Render Method
### 2. Write the Description

The JSDoc description is copied **verbatim** into `custom-elements.json`, the generated
Storybook `argTypes`/args interface, and the API docs of every framework wrapper
(Angular / React / Blazor). Write it as product documentation:

- **No `igc-` tag names in prose.** Use the plain-English component name — "the select
component", "toggle buttons", "the tile manager" — not `igc-select` or
`` `igc-toggle-button` ``. Tag names belong only in `@element` and fenced `@example`
blocks.
- **Don't restate that it is an attribute.** `@attr` already says so.
`The label of the control.` — not `The label attribute of the control.`
- **Don't use `Gets/Sets`.** State what the value is; if the setter has side effects, add a
second sentence for the behavior.
- **Booleans start with "Whether …"** and must describe the `true` state accurately. Verify
against the implementation — a `hide*`/`disable*` name inverts the sentence
(`hideIndicators` → *"Whether the carousel should skip rendering of the indicator
controls."*).
- **Present tense**, not "will" (`an empty value returns an empty string`).

```typescript
// ❌ Wrong
/**
* The outlined attribute of the control.
* @attr
*/

// ✅ Right
/**
* Whether the control has an outlined appearance.
* @attr
*/
```

Full reference: [create-new-component → Documentation Conventions](../create-new-component/SKILL.md#documentation-conventions)

### 3. Update Component Render Method

If the property affects rendering, update the `render()` method:

Expand All @@ -117,7 +153,7 @@ protected override render() {
}
```

### 3. Add Property Change Handler (if needed)
### 4. Add Property Change Handler (if needed)

If the property requires side effects or needs to sync computed/dependent properties, use Lit's lifecycle hooks.

Expand Down Expand Up @@ -153,7 +189,7 @@ protected override willUpdate(changedProperties: PropertyValues<this>): void {
- Always call `super.update(changedProperties)` when overriding `update()`
- Check `changedProperties.has()` to avoid unnecessary work

### 4. Update Tests
### 5. Update Tests

Add tests for the new property in `[component-name].spec.ts`:

Expand Down Expand Up @@ -187,13 +223,23 @@ it('reflects to attribute', async () => {
});
```

### 5. Update Storybook Story
### 6. Regenerate the Storybook Story Metadata

Add the property to `stories/[component-name].stories.ts`:
The `metadata` object, the `Igc[Component]Args` interface and their descriptions live inside
a **generated** `// region default … // endregion` block in
`stories/[component-name].stories.ts`. Do not hand-edit that block — regenerate it from the
JSDoc you just wrote:

**Update argTypes**:
```bash
npm run cem # regenerates custom-elements.json from the source JSDoc
npm run build:meta # rewrites the `// region default` block of each story
```

This produces the `argTypes` entry, the `args` default and the args-interface comment for
the new property, all carrying the description verbatim:

```typescript
// region default
argTypes: {
propertyName: {
type: 'string', // or 'boolean', 'number'
Expand All @@ -203,28 +249,25 @@ argTypes: {
},
// ... other properties
}
```

**Update args**:

```typescript
args: {
propertyName: defaultValue,
// ... other properties
}
```

**Update interface**:

```typescript
// ...
interface IgcComponentArgs {
/** [Property description] */
propertyName: PropertyType;
// ... other properties
}
// endregion
```

**Update story template**:
If the generated description reads badly, fix the JSDoc in the component and regenerate —
never patch the story. Every story is generated; there are no hand-maintained exceptions.

If your new property doesn't appear after regenerating, the story is being skipped silently.
Check that the filename matches the tag name (`igc-date-picker` → `date-picker.stories.ts`) and
that the `// region default` / `// endregion` pair is present — a missing region makes the
generator a no-op with no warning.

**Update the story template** — this part lives outside the generated region and is edited
by hand:

```typescript
export const Basic: Story = {
Expand All @@ -234,7 +277,7 @@ export const Basic: Story = {
};
```

### 6. Verify and Test
### 7. Verify and Test

Run tests and verify in Storybook:

Expand All @@ -253,15 +296,16 @@ npm run storybook

- [ ] Property added with `@property` decorator
- [ ] JSDoc comment includes `@attr` for primitives
- [ ] Description follows the [description rules](#2-write-the-description): no `igc-` tag
names, no "… attribute of the control", no `Gets/Sets`, booleans start with "Whether"
and describe the `true` state correctly
- [ ] Type annotation correct
- [ ] Default value appropriate
- [ ] `reflect: true` only for primitives
- [ ] Tests cover default value
- [ ] Tests cover property changes
- [ ] Tests cover attribute reflection (if applicable)
- [ ] Storybook argTypes updated
- [ ] Storybook args updated
- [ ] Storybook interface updated
- [ ] `npm run cem && npm run build:meta` run; generated story region committed
- [ ] Story template uses new property
- [ ] All tests pass
- [ ] Property works in Storybook
Expand All @@ -286,7 +330,22 @@ npm run storybook
### 4. Forgetting to Update Storybook

**Problem**: New property not controllable in Storybook
**Solution**: Add to argTypes, args, interface, and template
**Solution**: Run `npm run cem && npm run build:meta` to regenerate the `// region default`
block, then wire the property into the story template by hand

### 5. Hand-Editing the Generated Story Region

**Problem**: The `argTypes` description is fixed directly in the story; the next
`npm run build:meta` reverts it, or the story and the API docs disagree
**Solution**: The JSDoc in the component is the single source of truth — fix it there and
regenerate

### 6. Tag Names in the Description

**Problem**: A description like `Gets/Sets the name for all child igc-radio components.` ships
into `custom-elements.json`, the Storybook docs and every framework wrapper's API docs
**Solution**: `The name applied to all radio buttons in the group.` — see
[Write the Description](#2-write-the-description)

## Reference Examples

Expand Down
123 changes: 122 additions & 1 deletion .github/skills/create-new-component/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ declare global {
- **Organize code with region comments**: Internal state, Public properties, Lit lifecycle, Event handlers, Internal API, Public API
- **Prefix internal API** (private properties/methods) with underscore: `_internalMethod()`
- Add theming controller in constructor
- Include comprehensive JSDoc comments
- Include comprehensive JSDoc comments — follow [Documentation Conventions](#documentation-conventions)
- Declare global HTMLElementTagNameMap interface
- Use explicit return types for methods

Expand Down Expand Up @@ -387,6 +387,112 @@ npm run test
npm run storybook
```

## Documentation Conventions

Every JSDoc description on a public class, property, method, event, slot, CSS part or
CSS custom property is consumed **verbatim** by downstream tooling:

- `custom-elements.json` (generated by `npm run cem`)
- the generated `// region default` block in `stories/[name].stories.ts` (`npm run build:meta`)
- the published API docs and the framework wrappers (Angular / React / Blazor), where the
custom element tag name is either wrong or meaningless

So descriptions must read as product documentation, not as internal notes.

### Never put `igc-` tag names in prose

Refer to components by their plain-English name — "the carousel", "the tile manager",
"toggle buttons", "the select component". Never `igc-carousel`, `` `igc-toggle-button` ``
or `<igc-chat>`.

```typescript
// ❌ Wrong — tag names leak into the docs of every framework wrapper
/**
* The `igc-carousel` presents a set of `igc-carousel-slide`s.
*
* @slot - Renders `igc-toggle-button` component.
* @csspart svg - The igc-circular-progress SVG element.
*/

// ✅ Right
/**
* The carousel presents a set of slides.
*
* @slot - Renders the toggle buttons of the group.
* @csspart svg - The circular progress SVG element.
*/
```

Tag names are **only** allowed in:

- the `@element` tag itself — `@element igc-carousel`
- fenced `@example` code blocks (real markup/JS the user would write)
- literal event or attribute names that happen to contain `igc-` (e.g. the
`"igc-change-theme"` window event)
- `@internal` / `@hidden` members and non-exported internal controllers, mixins and
templates, where naming the concrete element is the point

### Describe the thing, not the attribute

The `@attr` tag already says it is an attribute. Don't repeat it in the sentence, and
don't use `Gets/Sets`.

| ❌ Avoid | ✅ Prefer |
| -------------------------------------------- | --------------------------------------------------------------- |
| `The label attribute of the control.` | `The label of the control.` |
| `The placeholder attribute of the control.` | `The placeholder text of the control.` |
| `The outlined attribute of the control.` | `Whether the control has an outlined appearance.` |
| `The autofocus attribute of the control.` | `Whether the control should receive focus automatically.` |
| `Gets/Sets the name for all child radios.` | `The name applied to all radio buttons in the group.` |
| `an empty value will return an empty string` | `an empty value returns an empty string` |

- Booleans start with **"Whether …"** and describe the `true` state accurately — check the
implementation, don't trust the property name (`hideIndicators` is *"Whether the carousel
should skip rendering of the indicator controls"*, not *"should render"*).
- Use present tense; avoid "will".
- If a setter has side effects beyond storing the value, state the value in the first
sentence and the behavior in a second one.
- Public methods that return something get an `@returns` tag.

### Tag layout

Keep the description as the leading summary paragraph and let the tags carry only their
own data. Don't append a description to `@element`:

```typescript
// ❌ Wrong
/**
* @element igc-select-group - A container for a group of select items.
*/

// ✅ Right
/**
* A container for a group of select items.
*
* @element igc-select-group
*/
```

### Regenerate after editing docs

Story metadata is generated, not hand-written. After changing any description run:

```bash
npm run cem # regenerates custom-elements.json from the JSDoc
npm run build:meta # rewrites the `// region default` block of each story
```

Every story is generated — there are no hand-maintained exceptions. Two failure modes will
silently skip a story, so check for both when your descriptions don't show up:

1. **Filename mismatch.** `build-stories.mjs` derives the filename from the tag name, so
`igc-date-picker` must live in `date-picker.stories.ts`. A mismatch logs
*"No story file found for …, skipping."* — easy to miss in the build output.
2. **Missing region markers.** If the file has no `// region default` / `// endregion` pair the
generator finds nothing to replace and writes nothing, **with no warning at all**. Wrap the
region from `const metadata` through `type Story = StoryObj<…>` inclusive — it also owns
`export default metadata;` and the args interface.

## Validation Checklist

Verify all of the following:
Expand All @@ -395,6 +501,8 @@ Verify all of the following:
- [ ] Extends `LitElement` (or appropriate mixin)
- [ ] `tagName` and `register()` static members defined
- [ ] JSDoc comments with `@element`, `@slot`, `@csspart`
- [ ] No `igc-` tag names in any description prose (see [Documentation Conventions](#documentation-conventions))
- [ ] `npm run cem && npm run build:meta` run, generated story region committed
- [ ] Theming controller added in constructor
- [ ] SCSS files exist in themes directory
- [ ] `themes.ts` aggregator imports all themes
Expand Down Expand Up @@ -443,6 +551,19 @@ Verify all of the following:
**Problem**: Exports not alphabetized in `src/index.ts`
**Solution**: Find correct alphabetical position before adding

### 8. Tag Names in Descriptions

**Problem**: A description reads `The prefix wrapper of the igc-textarea.` — the tag name is
copied verbatim into `custom-elements.json`, the Storybook docs and every framework wrapper's
API docs, where it is wrong or meaningless
**Solution**: Use the plain-English component name. See [Documentation Conventions](#documentation-conventions)

### 9. Editing Generated Story Metadata by Hand

**Problem**: A story's `// region default` block is edited directly and gets clobbered on the
next `npm run build:meta`, or the block drifts out of sync with the source JSDoc
**Solution**: Edit the JSDoc in the component, then run `npm run cem && npm run build:meta`

## Reference Examples

### Simple Display Component: Badge
Expand Down
Loading