diff --git a/.ai/rules/code-conformance.md b/.ai/rules/code-conformance.md index 511f538b86..7a8c0da97e 100644 --- a/.ai/rules/code-conformance.md +++ b/.ai/rules/code-conformance.md @@ -71,6 +71,7 @@ Reference: [Linting tools](../../CONTRIBUTOR-DOCS/02_style-guide/03_linting-tool - Forced-colors media query is present and correct (if applicable) - High-contrast and other media queries are sorted to the bottom of the file - No hard-coded values where design tokens are available +- Every class selector in CSS files (`.swc-*`) has a matching `class="..."` in the component's `render()` method; orphaned selectors mean styles are silently dead. Cross-check both directions: CSS → template and template → CSS. Use `grep -oE '\.[a-z][a-zA-Z-]+'` on the CSS and `grep -oE 'class="[^"]*"'` on the TypeScript to produce lists to compare. ## Test files diff --git a/.ai/skills/migration-styling/SKILL.md b/.ai/skills/migration-styling/SKILL.md index c9c08c2817..b9dbb1f324 100644 --- a/.ai/skills/migration-styling/SKILL.md +++ b/.ai/skills/migration-styling/SKILL.md @@ -66,6 +66,8 @@ Common case: confirming that subcomponent class names follow the single-hyphen s If a rename is needed, make the template change first, confirm the component still renders correctly in Storybook, then write the CSS. +**Step 3b — Audit `:host` for visual styles.** After aligning class names, scan every declaration you plan to put on `:host` against Rule 1 of the tldr. Ask for each property: is this layout-participation (how the host fits into its parent's flow) or visual (how the component looks)? If a visual style has no internal wrapper to move it to, add one to `render()` before writing CSS. + **Step 4 — Execute the phase.** Follow **[Phase 5: Styling](../../../CONTRIBUTOR-DOCS/03_project-planning/02_workstreams/02_2nd-gen-component-migration/02_step-by-step/01_washing-machine-workflow.md#phase-5-styling)** in the washing machine workflow doc — it covers what to do, what to check, common problems, and the quality gate for this phase. **Step 5 — Document exposed custom properties.** After writing the CSS, add a `@cssprop` JSDoc tag to the SWC component class (`2nd-gen/packages/swc/components/[component]/[Component].ts`) for every exposed `--swc-*` property. Place all `@cssprop` tags on the primary SWC class export (not the core base class). Each tag should name the property and give a one-line description of what it controls, including its default token where relevant. diff --git a/.ai/skills/migration-styling/references/tldr-component-css-guidelines.md b/.ai/skills/migration-styling/references/tldr-component-css-guidelines.md index ee1bf66e1e..e66ed7b0ff 100644 --- a/.ai/skills/migration-styling/references/tldr-component-css-guidelines.md +++ b/.ai/skills/migration-styling/references/tldr-component-css-guidelines.md @@ -72,9 +72,18 @@ ### 1. `:host` vs Component Class -Put only layout-participation styles on `:host`. Put actual visuals on `.swc-ComponentName` or internal parts. +Put layout-participation styles on `:host` (`display`, `inline-size`, `min-*`/`max-*`, `position`, custom property definitions). Put visual styles on `.swc-ComponentName` or an internal part. -**Exception**: three categories of styles may legitimately live on `:host` — each for a distinct reason: UA resets when the browser applies default styles directly to the host element (for example, the native popover stylesheet); transition properties (`opacity`, `transition-*`, `transition-behavior: allow-discrete`) when the host is itself the transition target (for example, when `@starting-style` applies to the host); and positioning surface (`position: absolute`, `inset: auto`, dimension constraints) when an external controller such as a placement controller writes coordinates directly to the host element. +**Exception**: three categories of styles may legitimately live on `:host`, each for a distinct reason: UA resets when the browser applies default styles directly to the host element (for example, the native popover stylesheet); transition properties (`opacity`, `transition-*`, `transition-behavior: allow-discrete`) when the host is itself the transition target (for example, when `@starting-style` applies to the host); and positioning surface (`position: absolute`, `inset: auto`, dimension constraints) when an external controller such as a placement controller writes coordinates directly to the host element. + +Two non-obvious cases to flag explicitly: + +- **`padding` on `:host`** — feels like layout but is visual spacing; move it to the internal class. +- **`cursor: pointer`** — do not set it anywhere; the project relies on browser defaults. + +Also check that `display: flex` or `display: grid` on `:host` is actually laying out **direct children of `:host`**, not internal children already wrapped inside a container element. Flex/grid properties (`flex: 1 1 auto`, `align-self`) only activate when their **immediate parent** is the flex/grid container — if the element is inside a wrapper div, the flex context must be on that wrapper, not on `:host`. + +**`:host:has()` is unreliable across browsers.** Safari and Firefox do not consistently support `:has()` relative to a shadow host boundary. Move all `:has()` selectors to the internal wrapper: `.swc-Component:has(...)` instead of `:host:has(...)`. Custom properties cascade identically either way. See [01_component-css#state-implementation-patterns](../../../../CONTRIBUTOR-DOCS/02_style-guide/01_css/01_component-css.md#state-implementation-patterns). → See [01_component-css#when-to-use-host](../../../../CONTRIBUTOR-DOCS/02_style-guide/01_css/01_component-css.md#when-to-use-host) @@ -136,6 +145,9 @@ Keep selector specificity at or below `(0,1,0)`. If you need a compounded select ### 7. Forced colors Only add `@media (forced-colors: active)` if browser defaults are not conveying correct semantic intent, and always put it at the end of the component stylesheet. + +Semantic HTML elements (`