feat: add web frontend rules and design quality hook

This commit is contained in:
Affaan Mustafa
2026-04-02 17:33:17 -07:00
parent a60d5fbc00
commit 31c9f7c33e
15 changed files with 772 additions and 2 deletions

96
rules/web/coding-style.md Normal file
View File

@@ -0,0 +1,96 @@
> This file extends [common/coding-style.md](../common/coding-style.md) with web-specific frontend content.
# Web Coding Style
## File Organization
Organize by feature or surface area, not by file type:
```text
src/
├── components/
│ ├── hero/
│ │ ├── Hero.tsx
│ │ ├── HeroVisual.tsx
│ │ └── hero.css
│ ├── scrolly-section/
│ │ ├── ScrollySection.tsx
│ │ ├── StickyVisual.tsx
│ │ └── scrolly.css
│ └── ui/
│ ├── Button.tsx
│ ├── SurfaceCard.tsx
│ └── AnimatedText.tsx
├── hooks/
│ ├── useReducedMotion.ts
│ └── useScrollProgress.ts
├── lib/
│ ├── animation.ts
│ └── color.ts
└── styles/
├── tokens.css
├── typography.css
└── global.css
```
## CSS Custom Properties
Define design tokens as variables. Do not hardcode palette, typography, or spacing repeatedly:
```css
:root {
--color-surface: oklch(98% 0 0);
--color-text: oklch(18% 0 0);
--color-accent: oklch(68% 0.21 250);
--text-base: clamp(1rem, 0.92rem + 0.4vw, 1.125rem);
--text-hero: clamp(3rem, 1rem + 7vw, 8rem);
--space-section: clamp(4rem, 3rem + 5vw, 10rem);
--duration-fast: 150ms;
--duration-normal: 300ms;
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
```
## Animation-Only Properties
Prefer compositor-friendly motion:
- `transform`
- `opacity`
- `clip-path`
- `filter` (sparingly)
Avoid animating layout-bound properties:
- `width`
- `height`
- `top`
- `left`
- `margin`
- `padding`
- `border`
- `font-size`
## Semantic HTML First
```html
<header>
<nav aria-label="Main navigation">...</nav>
</header>
<main>
<section aria-labelledby="hero-heading">
<h1 id="hero-heading">...</h1>
</section>
</main>
<footer>...</footer>
```
Do not reach for generic wrapper `div` stacks when a semantic element exists.
## Naming
- Components: PascalCase (`ScrollySection`, `SurfaceCard`)
- Hooks: `use` prefix (`useReducedMotion`)
- CSS classes: kebab-case or utility classes
- Animation timelines: camelCase with intent (`heroRevealTl`)

View File

@@ -0,0 +1,63 @@
> This file extends [common/patterns.md](../common/patterns.md) with web-specific design-quality guidance.
# Web Design Quality Standards
## Anti-Template Policy
Do not ship generic template-looking UI. Frontend output should look intentional, opinionated, and specific to the product.
### Banned Patterns
- Default card grids with uniform spacing and no hierarchy
- Stock hero section with centered headline, gradient blob, and generic CTA
- Unmodified library defaults passed off as finished design
- Flat layouts with no layering, depth, or motion
- Uniform radius, spacing, and shadows across every component
- Safe gray-on-white styling with one decorative accent color
- Dashboard-by-numbers layouts with sidebar + cards + charts and no point of view
- Default font stacks used without a deliberate reason
### Required Qualities
Every meaningful frontend surface should demonstrate at least four of these:
1. Clear hierarchy through scale contrast
2. Intentional rhythm in spacing, not uniform padding everywhere
3. Depth or layering through overlap, shadows, surfaces, or motion
4. Typography with character and a real pairing strategy
5. Color used semantically, not just decoratively
6. Hover, focus, and active states that feel designed
7. Grid-breaking editorial or bento composition where appropriate
8. Texture, grain, or atmosphere when it fits the visual direction
9. Motion that clarifies flow instead of distracting from it
10. Data visualization treated as part of the design system, not an afterthought
## Before Writing Frontend Code
1. Pick a specific style direction. Avoid vague defaults like "clean minimal".
2. Define a palette intentionally.
3. Choose typography deliberately.
4. Gather at least a small set of real references.
5. Use ECC design/frontend skills where relevant.
## Worthwhile Style Directions
- Editorial / magazine
- Neo-brutalism
- Glassmorphism with real depth
- Dark luxury or light luxury with disciplined contrast
- Bento layouts
- Scrollytelling
- 3D integration
- Swiss / International
- Retro-futurism
Do not default to dark mode automatically. Choose the visual direction the product actually wants.
## Component Checklist
- [ ] Does it avoid looking like a default Tailwind or shadcn template?
- [ ] Does it have intentional hover/focus/active states?
- [ ] Does it use hierarchy rather than uniform emphasis?
- [ ] Would this look believable in a real product screenshot?
- [ ] If it supports both themes, do both light and dark feel intentional?

120
rules/web/hooks.md Normal file
View File

@@ -0,0 +1,120 @@
> This file extends [common/hooks.md](../common/hooks.md) with web-specific hook recommendations.
# Web Hooks
## Recommended PostToolUse Hooks
Prefer project-local tooling. Do not wire hooks to remote one-off package execution.
### Format on Save
Use the project's existing formatter entrypoint after edits:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm prettier --write \"$FILE_PATH\"",
"description": "Format edited frontend files"
}
]
}
}
```
Equivalent local commands via `yarn prettier` or `npm exec prettier --` are fine when they use repo-owned dependencies.
### Lint Check
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm eslint --fix \"$FILE_PATH\"",
"description": "Run ESLint on edited frontend files"
}
]
}
}
```
### Type Check
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm tsc --noEmit --pretty false",
"description": "Type-check after frontend edits"
}
]
}
}
```
### CSS Lint
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm stylelint --fix \"$FILE_PATH\"",
"description": "Lint edited stylesheets"
}
]
}
}
```
## PreToolUse Hooks
### Guard File Size
Block oversized writes from tool input content, not from a file that may not exist yet:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller modules');process.exit(2)}console.log(d)})\"",
"description": "Block writes that exceed 800 lines"
}
]
}
}
```
## Stop Hooks
### Final Build Verification
```json
{
"hooks": {
"Stop": [
{
"command": "pnpm build",
"description": "Verify the production build at session end"
}
]
}
}
```
## Ordering
Recommended order:
1. format
2. lint
3. type check
4. build verification

79
rules/web/patterns.md Normal file
View File

@@ -0,0 +1,79 @@
> This file extends [common/patterns.md](../common/patterns.md) with web-specific patterns.
# Web Patterns
## Component Composition
### Compound Components
Use compound components when related UI shares state and interaction semantics:
```tsx
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">...</Tabs.Content>
<Tabs.Content value="settings">...</Tabs.Content>
</Tabs>
```
- Parent owns state
- Children consume via context
- Prefer this over prop drilling for complex widgets
### Render Props / Slots
- Use render props or slot patterns when behavior is shared but markup must vary
- Keep keyboard handling, ARIA, and focus logic in the headless layer
### Container / Presentational Split
- Container components own data loading and side effects
- Presentational components receive props and render UI
- Presentational components should stay pure
## State Management
Treat these separately:
| Concern | Tooling |
|---------|---------|
| Server state | TanStack Query, SWR, tRPC |
| Client state | Zustand, Jotai, signals |
| URL state | search params, route segments |
| Form state | React Hook Form or equivalent |
- Do not duplicate server state into client stores
- Derive values instead of storing redundant computed state
## URL As State
Persist shareable state in the URL:
- filters
- sort order
- pagination
- active tab
- search query
## Data Fetching
### Stale-While-Revalidate
- Return cached data immediately
- Revalidate in the background
- Prefer existing libraries instead of rolling this by hand
### Optimistic Updates
- Snapshot current state
- Apply optimistic update
- Roll back on failure
- Emit visible error feedback when rolling back
### Parallel Loading
- Fetch independent data in parallel
- Avoid parent-child request waterfalls
- Prefetch likely next routes or states when justified

64
rules/web/performance.md Normal file
View File

@@ -0,0 +1,64 @@
> This file extends [common/performance.md](../common/performance.md) with web-specific performance content.
# Web Performance Rules
## Core Web Vitals Targets
| Metric | Target |
|--------|--------|
| LCP | < 2.5s |
| INP | < 200ms |
| CLS | < 0.1 |
| FCP | < 1.5s |
| TBT | < 200ms |
## Bundle Budget
| Page Type | JS Budget (gzipped) | CSS Budget |
|-----------|---------------------|------------|
| Landing page | < 150kb | < 30kb |
| App page | < 300kb | < 50kb |
| Microsite | < 80kb | < 15kb |
## Loading Strategy
1. Inline critical above-the-fold CSS where justified
2. Preload the hero image and primary font only
3. Defer non-critical CSS or JS
4. Dynamically import heavy libraries
```js
const gsapModule = await import('gsap');
const { ScrollTrigger } = await import('gsap/ScrollTrigger');
```
## Image Optimization
- Explicit `width` and `height`
- `loading="eager"` plus `fetchpriority="high"` for hero media only
- `loading="lazy"` for below-the-fold assets
- Prefer AVIF or WebP with fallbacks
- Never ship source images far beyond rendered size
## Font Loading
- Max two font families unless there is a clear exception
- `font-display: swap`
- Subset where possible
- Preload only the truly critical weight/style
## Animation Performance
- Animate compositor-friendly properties only
- Use `will-change` narrowly and remove it when done
- Prefer CSS for simple transitions
- Use `requestAnimationFrame` or established animation libraries for JS motion
- Avoid scroll handler churn; use IntersectionObserver or well-behaved libraries
## Performance Checklist
- [ ] All images have explicit dimensions
- [ ] No accidental render-blocking resources
- [ ] No layout shifts from dynamic content
- [ ] Motion stays on compositor-friendly properties
- [ ] Third-party scripts load async/defer and only when needed

57
rules/web/security.md Normal file
View File

@@ -0,0 +1,57 @@
> This file extends [common/security.md](../common/security.md) with web-specific security content.
# Web Security Rules
## Content Security Policy
Always configure a production CSP.
### Nonce-Based CSP
Use a per-request nonce for scripts instead of `'unsafe-inline'`.
```text
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://*.example.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
```
Adjust origins to the project. Do not cargo-cult this block unchanged.
## XSS Prevention
- Never inject unsanitized HTML
- Avoid `innerHTML` / `dangerouslySetInnerHTML` unless sanitized first
- Escape dynamic template values
- Sanitize user HTML with a vetted local sanitizer when absolutely necessary
## Third-Party Scripts
- Load asynchronously
- Use SRI when serving from a CDN
- Audit quarterly
- Prefer self-hosting for critical dependencies when practical
## HTTPS and Headers
```text
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
```
## Forms
- CSRF protection on state-changing forms
- Rate limiting on submission endpoints
- Validate client and server side
- Prefer honeypots or light anti-abuse controls over heavy-handed CAPTCHA defaults

55
rules/web/testing.md Normal file
View File

@@ -0,0 +1,55 @@
> This file extends [common/testing.md](../common/testing.md) with web-specific testing content.
# Web Testing Rules
## Priority Order
### 1. Visual Regression
- Screenshot key breakpoints: 320, 768, 1024, 1440
- Test hero sections, scrollytelling sections, and meaningful states
- Use Playwright screenshots for visual-heavy work
- If both themes exist, test both
### 2. Accessibility
- Run automated accessibility checks
- Test keyboard navigation
- Verify reduced-motion behavior
- Verify color contrast
### 3. Performance
- Run Lighthouse or equivalent against meaningful pages
- Keep CWV targets from [performance.md](performance.md)
### 4. Cross-Browser
- Minimum: Chrome, Firefox, Safari
- Test scrolling, motion, and fallback behavior
### 5. Responsive
- Test 320, 375, 768, 1024, 1440, 1920
- Verify no overflow
- Verify touch interactions
## E2E Shape
```ts
import { test, expect } from '@playwright/test';
test('landing hero loads', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});
```
- Avoid flaky timeout-based assertions
- Prefer deterministic waits
## Unit Tests
- Test utilities, data transforms, and custom hooks
- For highly visual components, visual regression often carries more signal than brittle markup assertions
- Visual regression supplements coverage targets; it does not replace them