Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .claude/skills/css-nesting-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# CSS Nesting Conventions

## Overview

This project uses **native CSS nesting** (W3C CSS Nesting spec), not Sass/SCSS. They look similar but behave differently in one important way: native CSS does not support BEM-style `&` concatenation for child element selectors.

## The key rule

> `&` must be followed by a combinator (space, `>`, `~`, `+`) or a selector starting with `.`, `#`, `:`, `[`, or `*`. It **cannot** be followed by a bare identifier or `__` prefix.

## ❌ What NOT to do — Sass-style BEM concatenation

```css
/* Sass/SCSS — does NOT work in native CSS */
.demo-controls {
padding: 1.6rem;

&__heading { /* ← Invalid native CSS — esbuild converts to :is(__heading) */
font-size: 1.1rem;
}

&__fields { /* ← Invalid native CSS */
display: flex;
}
}
```

**Why it silently fails**: esbuild converts `&__heading` to `:is(__heading)`, which tries to match an HTML element named `__heading`. No such element exists, so the styles are never applied. There's no error — just missing styles.

The build will show this warning:
```
▲ [WARNING] Cannot use type selector "__heading" directly after nesting selector "&" [css-syntax-error]
CSS nesting syntax does not allow the "&" selector to come before a type selector.
```

## ✅ Correct patterns

### Nested descendant (preferred for BEM child elements)

```css
.demo-controls {
padding: 1.6rem;

& .demo-controls__heading { /* space + full class name */
font-size: 1.1rem;
}

& .demo-controls__fields {
display: flex;
}
}
```

### Flat top-level rules (also valid — avoids repetition for deeply nested structures)

```css
.demo-controls {
padding: 1.6rem;
}

.demo-controls__heading {
font-size: 1.1rem;
}

.demo-controls__fields {
display: flex;
}
```

### Same-element modifier (this IS valid)

```css
/* & followed by a class — matches the same element that also has this class */
.button {
background: blue;

&.button--large { /* ← valid: & immediately followed by . */
padding: 2rem;
}

&:hover { /* ← valid: & immediately followed by : */
background: darkblue;
}

&[disabled] { /* ← valid: & immediately followed by [ */
opacity: 0.5;
}
}
```

### Pseudo-elements and pseudo-classes

```css
.component {
&::before { content: ""; } /* ✅ valid */
&::after { content: ""; } /* ✅ valid */
&:focus { outline: auto; } /* ✅ valid */
&:not(.active) { opacity: 0.5; } /* ✅ valid */
}
```

### Media / container queries inside a rule

```css
.component {
grid-template-columns: 1fr;

@media (width >= 768px) {
grid-template-columns: 1fr 2fr; /* ✅ valid — query wraps the property */
}
}
```

## Spot-check during review

If you see `&__` or `&-` in a `.vue` `<style lang="css">` block, it's Sass syntax and will not work. Convert it to `& .full-class-name` or lift it to a top-level rule.

## Related

- `CLAUDE.md` → Styling Methodology section
- `css-grid-max-width-gutters.md` — example of correct native nesting in a grid utility
2 changes: 2 additions & 0 deletions .claude/skills/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
├── component-dynamic-slots.md — named dynamic slots ($slots iteration) vs indexed dynamic slots (itemCount pattern)
├── component-local-style-override.md — styleClassPassthrough + scoped style block for per-usage visual customisation
├── component-prop-driven-container-layout.md — vary CSS grid layout inside @container queries using data-* attribute selectors
├── css-nesting-conventions.md — native CSS nesting rules: why &__child Sass BEM concatenation silently breaks, correct patterns
├── css-grid-max-width-gutters.md — cap a centre grid column width by growing gutters, with start/center alignment variants
├── css-animation-utilities.md — scroll-driven animation utility classes: scroller-x (carousel), entry-zoom-reveal, entry-slide-in, entry-exit-blur, auto-rotate
├── component-aria-landmark.md — useAriaLabelledById composable: aria-labelledby for section/main/article/aside tags
Expand All @@ -44,6 +45,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
├── new-app-scaffold.md — scaffold a new Nuxt consumer app extending this layer (package.json, nuxt.config, app structure, CLAUDE.md)
├── qa-panel.md — collapsible dev-only panel for toggling component props live on a page (demo pages and consuming apps)
├── release-notes.md — produce release notes as a fenced markdown block from git log
├── pull-request-description.md — produce a PR description as a fenced markdown block from git diff vs main
├── composable-canonical-url.md — useCanonicalUrl: set <link rel="canonical"> from runtimeConfig.public.canonicalHost; layout setup, node types
├── composable-whatsapp.md — useWhatsApp: open pre-filled wa.me link from form payload; runtime config, security, usage
├── composable-zod-validation.md — useZodValidation: schema-driven form validation, error binding, submit flow, API error push
Expand Down
48 changes: 48 additions & 0 deletions .claude/skills/pull-request-description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Pull Request Description

## Overview

When asked to create a pull request description, produce a fenced markdown code block (` ```markdown `) so the content can be copied and pasted directly into GitHub's PR body field without formatting being stripped.

## Steps

### 1. Identify the base branch

Run `git log --oneline main..HEAD` to list all commits on the current branch since it diverged from `main`.

### 2. Review what changed

Run `git diff main...HEAD --stat` for a file-level summary, then `git diff main...HEAD` for the full diff. Focus on intent, not just mechanics.

### 3. Produce a fenced markdown block

Always wrap the output in a ` ```markdown ` code fence — never render it as plain markdown.

## Format

```markdown
## Summary

One or two sentences explaining what this PR does and why. No bullet points here — write it as prose.

## Changes

- **`FileOrComponentName`** — what changed and why
- Keep each bullet to one line where possible

## Testing

- How the change was verified (unit tests, manual check, build passing, etc.)
- Note any areas that couldn't be covered automatically

## Notes

Any caveats, follow-up tickets, or decisions worth flagging for reviewers (optional — omit if nothing to say).
```

## Notes

- Only include sections that have content — omit empty headings (especially `Notes` if there's nothing to flag)
- Lead with the user-facing or functional change; CSS/test/doc tidy-ups can be secondary bullets
- Keep the tone factual — describe what changed, not the effort involved
- Do not include "Co-Authored-By" or other git trailer lines — those belong in the commit message, not the PR body
6 changes: 2 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,10 @@
"editor.trimAutoWhitespace": "explicit"
}
},
// Use workspace TypeScript (5.9.x) instead of VS Code's built-in version
"typescript.tsdk": "node_modules/typescript/lib",

// More info: https://open-props.style/#getting-started
"cssvar.files": ["assets/styles/main.css"],
"cssvar.ignore": [],
"cssvar.extensions": ["css", "sss", "postcss", "vue", "ts"],
"js/ts.tsdk.path": "node_modules/typescript/lib"
"js/ts.tsdk.path": "node_modules/typescript/lib",
"git.addAICoAuthor": "off"
}
1 change: 1 addition & 0 deletions Claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ See `.claude/skills/storybook-add-font.md` for the step-by-step process to add a
8. **Incorrect type casting**: Use `as unknown as CustomType` for component instances
9. **Unmocked browser APIs**: Always mock ResizeObserver, IntersectionObserver, etc.
10. **Missing DOM element casting**: Cast to HTMLElement when accessing style properties
11. **Sass-style BEM nesting in native CSS**: Never use `&__child` or `&-modifier` concatenation — this is Sass syntax and does not work in native CSS. esbuild silently converts `&__foo` to `:is(__foo)` which matches nothing. Use `& .block__child` (descendant selector) or a top-level `.block__child {}` rule instead. See `.claude/skills/css-nesting-conventions.md`.

## Development Workflow

Expand Down
Loading
Loading