From adlc-react-frontend
Protocol for adding functionality to existing React components while maintaining backward compatibility.
npx claudepluginhub sumanpapanaboina1983/adlc-accelerator-kit-pluginsThis skill uses the workspace's default tool permissions.
| Gate | Threshold | Status |
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
| Gate | Threshold | Status |
|---|---|---|
| TypeScript | 0 type errors | REQUIRED |
| Tests | 100% pass (0 failures) | REQUIRED |
| Coverage | ≥ 80% line coverage | REQUIRED |
| ESLint | 0 errors (warnings OK) | REQUIRED |
| Build | must pass | REQUIRED |
⛔ BLOCKING: Enhancement is NOT complete until ALL gates pass.
Before ANY enhancement, verify Vitest coverage thresholds are configured:
grep -l "thresholds" vitest.config.ts
If NOT configured, update vitest.config.ts first (see react-testing skill for full configuration):
// vitest.config.ts - coverage section
coverage: {
provider: 'v8',
reporter: ['text', 'text-summary', 'html', 'lcov'],
thresholds: {
lines: 80,
branches: 70,
functions: 80,
statements: 80,
},
}
When adding to EXISTING code (not building from scratch):
┌─────────────────────────────────────────────────────────────┐
│ STEP 0: VERIFY COVERAGE CONFIGURATION │
│ - Check: grep "thresholds" vitest.config.ts │
│ - If missing → Configure coverage thresholds FIRST │
│ - ⛔ Do NOT proceed without coverage configuration │
│ │
│ STEP 1: READ EXISTING CODE FIRST │
│ - Read the existing component │
│ - Understand current patterns, props, state │
│ - Note: styling approach, event handling, types │
│ │
│ STEP 2: IDENTIFY PATTERNS TO FOLLOW │
│ - How are props defined? │
│ - What naming conventions are used? │
│ - How is styling done (Tailwind classes)? │
│ - How are tests organized? │
│ │
│ STEP 3: WRITE NEW TESTS ONLY │
│ - Add NEW test cases — don't modify existing tests │
│ - Follow existing test patterns │
│ - Cover: new behavior, edge cases │
│ │
│ STEP 4: IMPLEMENT FOLLOWING EXISTING PATTERNS │
│ - Match existing code style exactly │
│ - Use same prop naming patterns │
│ - Follow same styling approach │
│ │
│ STEP 5: VERIFY NO REGRESSIONS │
│ - Run FULL test suite (existing + new) │
│ - All existing tests must still pass │
│ - No changes to existing behavior │
└─────────────────────────────────────────────────────────────┘
Before adding ANY code, read and note:
# Read the existing component
Read src/components/TodoItem.tsx
# Note: Props interface, state, event handlers, styling
# Read existing tests
Read src/components/TodoItem.test.tsx
# Note: Test patterns, queries used, assertions
# Read related components
Read src/components/TodoList.tsx
# Note: How parent uses this component
# Read types if separate
Read src/types/todo.ts
# Note: Existing type definitions
| Aspect | Question to Answer |
|---|---|
| Props Interface | Named XxxProps? Extends HTML attributes? |
| Export Style | Default export or named export? |
| Styling | Tailwind? CSS modules? Styled-components? |
| Event Handlers | onXxx naming? Inline or extracted? |
| State Management | Local state? Context? External store? |
| Test Queries | getByRole? getByText? getByTestId? |
| Test Naming | "should X when Y"? "it X"? |
disabled prop to ButtonStep 1: Read existing component
// EXISTING Button.tsx
interface ButtonProps {
children: React.ReactNode;
onClick: () => void;
variant?: 'primary' | 'secondary';
}
export default function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
return (
<button
onClick={onClick}
className={clsx(
'px-4 py-2 rounded-lg font-medium',
variant === 'primary' ? 'bg-blue-500 text-white' : 'bg-gray-200'
)}
>
{children}
</button>
);
}
Step 2: Identify patterns
XxxProps interfaceStep 3: Write NEW tests
// ADD to existing Button.test.tsx
describe('disabled state', () => {
it('should be disabled when disabled prop is true', () => {
render(<Button onClick={vi.fn()} disabled>Click</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
it('should not call onClick when disabled', async () => {
const onClick = vi.fn();
const user = userEvent.setup();
render(<Button onClick={onClick} disabled>Click</Button>);
await user.click(screen.getByRole('button'));
expect(onClick).not.toHaveBeenCalled();
});
it('should have disabled styling when disabled', () => {
render(<Button onClick={vi.fn()} disabled>Click</Button>);
const button = screen.getByRole('button');
expect(button).toHaveClass('opacity-50', 'cursor-not-allowed');
});
});
Step 4: Implement following patterns
// ENHANCED Button.tsx
interface ButtonProps {
children: React.ReactNode;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean; // NEW - optional with default
}
export default function Button({
children,
onClick,
variant = 'primary',
disabled = false, // NEW - with default
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled} // NEW
className={clsx(
'px-4 py-2 rounded-lg font-medium',
variant === 'primary' ? 'bg-blue-500 text-white' : 'bg-gray-200',
disabled && 'opacity-50 cursor-not-allowed' // NEW
)}
>
{children}
</button>
);
}
Step 5: Run full suite
npm test # ALL tests: existing + new must pass
// EXISTING
interface TodoItemProps {
id: string;
title: string;
}
// ENHANCED - ADD optional props only
interface TodoItemProps {
id: string;
title: string;
priority?: 'low' | 'medium' | 'high'; // NEW - optional
onPriorityChange?: (priority: string) => void; // NEW - optional
}
// EXISTING
type Status = 'active' | 'completed';
// ENHANCED - ADD to union
type Status = 'active' | 'completed' | 'archived'; // SAFE if handled
// BREAKING: Changing prop type
// Before: onClick: () => void
// After: onClick: (id: string) => void // Breaks existing usage!
// BREAKING: Renaming prop
// Before: onComplete
// After: onToggle // Breaks existing usage!
// BREAKING: Removing prop
// Before: { showIcon?: boolean }
// After: { } // Breaks existing usage!
// BREAKING: Making optional prop required
// Before: { size?: 'sm' | 'lg' }
// After: { size: 'sm' | 'lg' } // Breaks existing usage!
Read existing component first:
// EXISTING TodoList.tsx
interface TodoListProps {
todos: Todo[];
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}
export default function TodoList({ todos, onToggle, onDelete }: TodoListProps) {
return (
<ul className="space-y-2">
{todos.map(todo => (
<TodoItem key={todo.id} {...todo} onToggle={onToggle} onDelete={onDelete} />
))}
</ul>
);
}
Write NEW tests:
// ADD to TodoList.test.tsx
describe('filtering', () => {
const todos = [
{ id: '1', title: 'Active', completed: false },
{ id: '2', title: 'Completed', completed: true },
];
it('should show all todos when filter is "all"', () => {
render(<TodoList todos={todos} filter="all" {...handlers} />);
expect(screen.getByText('Active')).toBeInTheDocument();
expect(screen.getByText('Completed')).toBeInTheDocument();
});
it('should show only active todos when filter is "active"', () => {
render(<TodoList todos={todos} filter="active" {...handlers} />);
expect(screen.getByText('Active')).toBeInTheDocument();
expect(screen.queryByText('Completed')).not.toBeInTheDocument();
});
it('should show only completed todos when filter is "completed"', () => {
render(<TodoList todos={todos} filter="completed" {...handlers} />);
expect(screen.queryByText('Active')).not.toBeInTheDocument();
expect(screen.getByText('Completed')).toBeInTheDocument();
});
it('should default to showing all todos when no filter provided', () => {
render(<TodoList todos={todos} {...handlers} />);
expect(screen.getByText('Active')).toBeInTheDocument();
expect(screen.getByText('Completed')).toBeInTheDocument();
});
});
Implement following patterns:
// ENHANCED TodoList.tsx
type FilterType = 'all' | 'active' | 'completed';
interface TodoListProps {
todos: Todo[];
onToggle: (id: string) => void;
onDelete: (id: string) => void;
filter?: FilterType; // NEW - optional to maintain compatibility
}
export default function TodoList({
todos,
onToggle,
onDelete,
filter = 'all', // NEW - defaults to 'all' for backward compatibility
}: TodoListProps) {
// NEW - filter logic
const filteredTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter(t => !t.completed);
case 'completed':
return todos.filter(t => t.completed);
default:
return todos;
}
}, [todos, filter]);
return (
<ul className="space-y-2">
{filteredTodos.map(todo => (
<TodoItem key={todo.id} {...todo} onToggle={onToggle} onDelete={onDelete} />
))}
</ul>
);
}
// DON'T change existing prop names
// Before: onToggle → After: onComplete ❌
// DON'T change prop types
// Before: id: string → After: id: number ❌
// DON'T modify existing test assertions
// If existing test expects X and you need Y, that's breaking ❌
// DON'T remove existing functionality
// Before: <Button size="sm" /> works
// After: <Button size="sm" /> breaks ❌
// DON'T change default behavior
// Before: default variant is 'primary'
// After: default variant is 'secondary' ❌
Before declaring done:
Before marking enhancement complete:
# Generate coverage report
npm test -- --coverage --run
# Check coverage
open coverage/lcov-report/index.html
| Metric | Minimum Threshold |
|---|---|
| Line Coverage | 80% |
| Branch Coverage | 75% |
| Function Coverage | 80% |
If coverage < 80%:
npm test -- --coverage⛔ Do NOT mark complete if coverage < 80%
A pre-completion hook enforces this requirement automatically.