initial commit

Signed-off-by: Shreyans Jain <shreyans@shreyans.sh>
This commit is contained in:
Shreyans Jain
2026-02-06 08:18:06 -08:00
commit e2f9b100f5
4 changed files with 420 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
# AGENTS.md
This file provides guidance to AI coding agents (Claude Code, Cursor, Copilot, etc.) when working with code in this repository.
## Repository Overview
A collection of skills for coding agents. Skills are packaged instructions and scripts that extend agent's capabilities.
## Creating a New Skill
### Directory Structure
```
skills/
{skill-name}/ # kebab-case directory name
SKILL.md # Required: skill definition
scripts/ # Required: executable scripts
{script-name}.sh # Bash scripts (preferred)
{skill-name}.zip # Required: packaged for distribution
```
### Naming Conventions
- **Skill directory**: `kebab-case` (e.g., `vercel-deploy`, `log-monitor`)
- **SKILL.md**: Always uppercase, always this exact filename
- **Scripts**: `kebab-case.sh` (e.g., `deploy.sh`, `fetch-logs.sh`)
- **Zip file**: Must match directory name exactly: `{skill-name}.zip`
### SKILL.md Format
````markdown
---
name: { skill-name }
description:
{
One sentence describing when to use this skill. Include trigger phrases like "Deploy my app",
"Check logs",
etc.,
}
---
# {Skill Title}
{Brief description of what the skill does.}
## How It Works
{Numbered list explaining the skill's workflow}
## Usage
```bash
bash /mnt/skills/user/{skill-name}/scripts/{script}.sh [args]
```
````
**Arguments:**
- `arg1` - Description (defaults to X)
**Examples:**
{Show 2-3 common usage patterns}
## Output
{Show example output users will see}
## Present Results to User
{Template for how agent should format results when presenting to users}
## Troubleshooting
{Common issues and solutions, especially network/permissions errors}
````
### Best Practices for Context Efficiency
Skills are loaded on-demand — only the skill name and description are loaded at startup. The full `SKILL.md` loads into context only when the agent decides the skill is relevant. To minimize context usage:
- **Keep SKILL.md under 500 lines** — put detailed reference material in separate files
- **Write specific descriptions** — helps the agent know exactly when to activate the skill
- **Use progressive disclosure** — reference supporting files that get read only when needed
- **Prefer scripts over inline code** — script execution doesn't consume context (only output does)
- **File references work one level deep** — link directly from SKILL.md to supporting files
### Script Requirements
- Use `#!/bin/bash` shebang
- Use `set -e` for fail-fast behavior
- Write status messages to stderr: `echo "Message" >&2`
- Write machine-readable output (JSON) to stdout
- Include a cleanup trap for temp files
- Reference the script path as `/mnt/skills/user/{skill-name}/scripts/{script}.sh`
### End-User Installation
```bash
npx skills add CodeWithShreyans/skills --skill <skill-name>
```
````
+59
View File
@@ -0,0 +1,59 @@
# Agent Skills
A collection of skills for AI coding agents. Skills are packaged instructions and references that extend agent capabilities.
Skills follow the [Agent Skills](https://agentskills.io/) format.
## Available Skills
### bulletproof-react-components
Nine patterns for building React components that survive real-world conditions — SSR, hydration, concurrent rendering, portals, transitions, and future React changes. Based on [Shu Ding's guide](https://shud.in/thoughts/build-bulletproof-react-components).
**Use when:**
- Writing reusable React components
- Fixing hydration mismatches
- Handling SSR edge cases
- Building component libraries
**Patterns covered:**
- Server-Proof (Critical) - no browser APIs during render
- Hydration-Proof (Critical) - inline scripts before hydration
- Instance-Proof (High) - `useId()` over hardcoded IDs
- Concurrent-Proof (High) - `React.cache()` deduplication
- Composition-Proof (High) - Context over `cloneElement`
- Portal-Proof (Medium) - `ownerDocument.defaultView` for listeners
- Transition-Proof (Medium) - `startTransition()` for View Transitions
- Activity-Proof (Medium) - `useLayoutEffect` for `<Activity>` visibility
- Future-Proof (Medium) - `useState` initializer for stable values
## Installation
```bash
npx skills add shreyans/skills
```
## Usage
Skills are automatically available once installed. The agent will use them when relevant tasks are detected.
**Examples:**
```
Review this component for bulletproof patterns
```
```
Make this component SSR-safe
```
```
Check this component for hydration issues
```
## Skill Structure
Each skill contains:
- `SKILL.md` - Instructions for the agent
- `references/` - Supporting documentation loaded on demand
## License
MIT
@@ -0,0 +1,64 @@
---
name: bulletproof-react-components
description: Build bulletproof React components that survive SSR, hydration, concurrent rendering, portals, transitions, and future React changes. Nine essential patterns from Shu Ding's guide. Use when writing reusable React components, fixing hydration mismatches, handling SSR edge cases, or building component libraries.
---
# Bulletproof React Components
Nine patterns that ensure React components survive real-world conditions beyond the happy path — SSR, hydration, concurrent rendering, portals, and more.
Source: [shud.in/thoughts/build-bulletproof-react-components](https://shud.in/thoughts/build-bulletproof-react-components)
## How It Works
1. When writing or reviewing a reusable React component, consult the **Quick Rules** below
2. For code examples and deeper explanation, read `./references/patterns.md`
3. Run through the **Checklist** before shipping
## Quick Rules
| # | Pattern | Rule |
|---|---------|------|
| 1 | **Server-Proof** | Never call browser APIs (`localStorage`, `window`, `document`) during render. Use `useEffect`. |
| 2 | **Hydration-Proof** | Inject a synchronous inline `<script>` to set client-dependent values before React hydration. |
| 3 | **Instance-Proof** | Use `useId()` for all generated IDs. Never hardcode IDs in reusable components. |
| 4 | **Concurrent-Proof** | Wrap server data-fetching in `React.cache()` to deduplicate calls per request. |
| 5 | **Composition-Proof** | Use Context instead of `React.cloneElement()` — cloneElement breaks with Server Components, lazy, and memo. |
| 6 | **Portal-Proof** | Use `ref.current?.ownerDocument.defaultView \|\| window` for event listeners, not the global `window`. |
| 7 | **Transition-Proof** | Wrap state updates in `startTransition()` to enable View Transition API animations. |
| 8 | **Activity-Proof** | Use `useLayoutEffect` to disable DOM side effects (e.g., `<style>` tags) when hidden by `<Activity>`. |
| 9 | **Future-Proof** | Use `useState(() => value)` for stable identity. `useMemo` is only a performance hint — React may discard it. |
## Checklist
When building a reusable React component, verify:
- [ ] No browser APIs called during render (server-proof)
- [ ] No hydration flash for client-storage values (hydration-proof)
- [ ] No hardcoded IDs; uses `useId()` (instance-proof)
- [ ] Server data fetches wrapped in `cache()` (concurrent-proof)
- [ ] Uses Context instead of `cloneElement` (composition-proof)
- [ ] Event listeners use `ownerDocument.defaultView` (portal-proof)
- [ ] State updates wrapped in `startTransition()` where needed (transition-proof)
- [ ] DOM side effects respect `<Activity>` visibility (activity-proof)
- [ ] Stable values use `useState` initializer, not `useMemo` (future-proof)
## Present Results to User
When reviewing a component against these patterns, format as:
```
**Bulletproof Check: `<ComponentName>`**
| Pattern | Status | Notes |
|---------|--------|-------|
| Server-Proof | PASS/FAIL | ... |
| ... | ... | ... |
**Suggested fixes:**
1. ...
```
## References
- `./references/patterns.md` — Detailed code examples (bad/good) for all nine patterns
@@ -0,0 +1,195 @@
# Bulletproof React Patterns — Code Examples
Detailed bad/good code examples for each of the nine patterns.
---
## 1. Server-Proof
Browser APIs crash during SSR. Move them into `useEffect`.
```tsx
// BAD - crashes on server
const theme = localStorage.getItem('theme')
// GOOD - safe for SSR
const [theme, setTheme] = useState('light')
useEffect(() => {
setTheme(localStorage.getItem('theme') || 'light')
}, [])
```
---
## 2. Hydration-Proof
Server renders initial state, client hydrates with different values, causing visual flashes. Inject a synchronous inline script that sets the correct value before React hydration.
```tsx
// Inline script runs before React hydration — no flash
<script dangerouslySetInnerHTML={{
__html: `document.documentElement.dataset.theme = localStorage.getItem('theme') || 'light'`
}} />
```
This avoids the flash-of-wrong-content that happens when `useEffect` corrects the value after paint.
---
## 3. Instance-Proof
Multiple instances with hardcoded IDs conflict. Use `useId()`.
```tsx
// BAD - breaks with multiple instances
const id = 'my-tooltip'
// GOOD - unique per instance, stable across server/client
const id = useId()
return (
<>
<button aria-describedby={id}>Hover me</button>
<div id={id} role="tooltip">Tooltip content</div>
</>
)
```
---
## 4. Concurrent-Proof
Server Components fetched multiple times cause duplicate queries. Wrap in `React.cache()`.
```tsx
import { cache } from 'react'
// Deduplicated across all Server Components in the same request
const getUser = cache(async (id: string) => {
return await db.user.findUnique({ where: { id } })
})
// Both components call getUser('123') but only one DB query executes
async function UserProfile({ id }: { id: string }) {
const user = await getUser(id)
return <h1>{user.name}</h1>
}
async function UserAvatar({ id }: { id: string }) {
const user = await getUser(id)
return <img src={user.avatar} />
}
```
---
## 5. Composition-Proof
`React.cloneElement()` fails with Server Components, lazy-loaded components, or memo. Use Context.
```tsx
// BAD - breaks with Server Components, React.lazy, React.memo
React.Children.map(children, child =>
React.cloneElement(child, { active: true })
)
// GOOD - works with any child type
const TabContext = createContext({ activeIndex: 0 })
function Tabs({ children, activeIndex }: Props) {
return (
<TabContext.Provider value={{ activeIndex }}>
{children}
</TabContext.Provider>
)
}
function Tab({ index, children }: TabProps) {
const { activeIndex } = useContext(TabContext)
return <div data-active={index === activeIndex}>{children}</div>
}
```
---
## 6. Portal-Proof
Event listeners on `window` fail in portals, iframes, pop-out windows. Use the component's actual window via its DOM ref.
```tsx
// BAD - only works in the main window
useEffect(() => {
window.addEventListener('keydown', handler)
return () => window.removeEventListener('keydown', handler)
}, [])
// GOOD - works in portals, iframes, pop-out windows
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const win = ref.current?.ownerDocument.defaultView || window
win.addEventListener('keydown', handler)
return () => win.removeEventListener('keydown', handler)
}, [])
return <div ref={ref}>...</div>
```
---
## 7. Transition-Proof
View Transitions don't animate without `startTransition()`.
```tsx
import { startTransition } from 'react'
function handleNavigate() {
// Enables View Transition API animation
startTransition(() => {
setCurrentPage(nextPage)
})
}
```
---
## 8. Activity-Proof
DOM-level side effects (like `<style>` tags) persist globally even when hidden by `<Activity>`. Use `useLayoutEffect` to disable them.
```tsx
const ref = useRef<HTMLStyleElement>(null)
// Cleanup runs when Activity hides; re-runs when visible
useLayoutEffect(() => {
if (ref.current) {
ref.current.media = 'all' // Enable styles when visible
}
return () => {
if (ref.current) {
ref.current.media = 'not all' // Disable styles when hidden
}
}
}, [])
return <style ref={ref}>{css}</style>
```
---
## 9. Future-Proof
`useMemo` is only a performance hint — React may discard cached values. Use `useState` with an initializer for values that must persist.
```tsx
// BAD - React may discard this at any time
const stableId = useMemo(() => crypto.randomUUID(), [])
// GOOD - useState guarantees persistence for the component's lifetime
const [stableId] = useState(() => crypto.randomUUID())
```
**When to use which:**
- `useMemo`: Recomputable values where recalculation is just expensive (derived data, filtered lists)
- `useState` initializer: Values where identity/stability matters (IDs, subscriptions, one-time setup)