Tuning Panel
Tuning Panel Skill
Create bespoke parameter tuning panels that give users visual control over values they're iterating on. These panels surface all relevant parameters for the current task, enable real-time adjustment, and export tuned values in an LLM-friendly format.
Core Philosophy
Err on the side of exhaustive. When a user is tuning something, surface every parameter that could reasonably affect the outcome. Missing a parameter forces context-switching; having "too many" parameters costs only scroll distance.
Platform-native approach. Use the most appropriate library for the codebase:
- React →
leva(preferred) orreact-dat-gui - Vue →
tweakpanewith Vue bindings - Vanilla JS →
tweakpaneordat.GUI - Swift/SwiftUI → Native controls with
@Statebindings - Flutter → Debug overlay with
ValueNotifier
Implementation Workflow
Step 1: Identify All Tunable Parameters
Analyze the code being tuned and extract every parameter that affects the output. Organize by category:
Animation Parameters:
- Duration (ms)
- Delay (ms)
- Easing function (with presets + custom bezier)
- Direction, iteration count, fill mode
- Spring physics: stiffness, damping, mass, velocity
Layout Parameters:
- Spacing: padding, margin, gap (all sides)
- Sizing: width, height, min/max constraints
- Position: x, y, z-index
- Flex/Grid: alignment, justify, grow, shrink
Visual Parameters:
- Colors: with alpha, HSL mode for easier tuning
- Opacity, blur, shadows (offset, blur, spread, color)
- Border: width, radius (per corner), style
- Transforms: scale, rotate, translate, skew
Typography Parameters:
- Font size, line height, letter spacing, word spacing
- Font weight (as slider 100-900)
- Text alignment, decoration, transform
Custom Domain Parameters:
- Physics: gravity, friction, bounce, mass
- Audio: volume, pan, pitch, attack, decay
- 3D: FOV, near/far planes, light intensity
Step 2: Create Debug-Mode Panel
Wrap the tuning panel in a debug/development mode check so it never appears in production:
// React pattern
const TuningPanel = () => {
if (process.env.NODE_ENV !== 'development') return null;
// ... panel implementation
};
// Swift pattern
#if DEBUG
struct TuningPanel: View { ... }
#endif
Step 3: Implement Platform-Specific Panel
Consult references/platform-libraries.md for detailed implementation patterns. Key principles:
- Group related parameters using folders/sections
- Use appropriate control types: sliders for numbers, color pickers for colors, dropdowns for enums
- Set sensible min/max/step values based on the parameter domain
- Include presets for common configurations
- Add reset buttons to return to defaults
Step 4: Add LLM Export Functionality
CRITICAL: The export functionality must properly track and display only the values that have actually been changed from defaults. This is essential for users to see what they've tuned.
Implementation Requirements
- Store defaults at component level - Define a
defaultsobject beforeuseControls() - Use proper comparison - Handle floating point numbers with tolerance (e.g., 0.001)
- Filter changed values only - Don't show "X → X" for unchanged values
- Format clearly - Make parameter names human-readable
Correct Implementation Pattern
export default function TunableComponent() {
// CRITICAL: Store defaults at component level for comparison
const defaults = {
duration: 300,
delay: 0,
stiffness: 100,
damping: 10,
opacity: 1.0,
};
const config = useControls({
animation: folder({
duration: { value: 300, min: 0, max: 2000, step: 10 },
delay: { value: 0, min: 0, max: 1000, step: 10 },
}),
physics: folder({
stiffness: { value: 100, min: 0, max: 300, step: 1 },
damping: { value: 10, min: 0, max: 100, step: 1 },
}),
visual: folder({
opacity: { value: 1.0, min: 0, max: 1, step: 0.01 },
}),
'Export for LLM': button(() => {
const formatted = `## Tuned Parameters
Apply these values to the component:
\`\`\`typescript
const config = {
${Object.entries(config)
.filter(([key]) => key !== 'Export for LLM')
.map(([key, val]) => ` ${key}: ${JSON.stringify(val)},`)
.join('\n')}
};
\`\`\`
### Changes from Defaults
${Object.entries(config)
.filter(([key, val]) => {
const defaultVal = defaults[key as keyof typeof defaults];
if (defaultVal === undefined) return false;
// Use tolerance for floating point comparison
const numVal = Number(val);
const numDefault = Number(defaultVal);
if (!isNaN(numVal) && !isNaN(numDefault)) {
return Math.abs(numVal - numDefault) > 0.001;
}
return val !== defaultVal;
})
.map(([key, val]) => {
const defaultVal = defaults[key as keyof typeof defaults];
// Convert camelCase to Title Case for display
const displayKey = key.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase());
const formattedDefault = typeof defaultVal === 'number'
? defaultVal.toFixed(2) : defaultVal;
const formattedVal = typeof val === 'number'
? val.toFixed(2) : val;
return `- ${displayKey}: ${formattedDefault} → ${formattedVal}`;
})
.join('\n') || '(No changes from defaults)'}
`;
navigator.clipboard.writeText(formatted);
alert('Tuned parameters copied to clipboard!');
}),
});
return <Component {...config} />;
}
Common Mistakes to Avoid
❌ DON'T hardcode defaults in the export string:
// BAD - Shows "Default: 300 → ${config.duration}" even if unchanged
`- Duration: 300 → ${config.duration}`
❌ DON'T use strict equality for numbers:
// BAD - Floating point comparison may fail
.filter(([key, val]) => val !== defaults[key])
❌ DON'T forget to store defaults:
// BAD - No defaults object to compare against
const config = useControls({
duration: { value: 300, min: 0, max: 2000 },
});
✅ DO store defaults and filter properly:
// GOOD - Proper defaults tracking and comparison
const defaults = { duration: 300, delay: 0 };
const config = useControls({ /* ... */ });
const changed = Object.entries(config).filter(([k, v]) =>
Math.abs(Number(v) - Number(defaults[k])) > 0.001
);
The export should include:
- All current values in a code-ready format
- Only changed values in the diff section (critical!)
- Context about what was being tuned
- Human-readable parameter names
Parameter Discovery Strategies
When analyzing code to find tunable parameters:
- Search for magic numbers - Any hardcoded numeric value
- Look for style objects - CSS-in-JS, inline styles, theme values
- Find animation definitions - Framer Motion, CSS transitions, GSAP
- Identify color values - Hex, RGB, HSL anywhere in the file
- Check component props - Props with numeric or color defaults
- Examine CSS custom properties -
--var-namedeclarations
Debug Mode Patterns
React (Environment Variable)
const DevTools = lazy(() => import('./TuningPanel'));
function App() {
return (
<>
<MainContent />
{process.env.NODE_ENV === 'development' && (
<Suspense fallback={null}>
<DevTools />
</Suspense>
)}
</>
);
}
React (Keyboard Shortcut)
const [showPanel, setShowPanel] = useState(false);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.metaKey && e.shiftKey && e.key === 'D') {
setShowPanel(prev => !prev);
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, []);
Swift (Debug Build Flag)
#if DEBUG
@State private var showTuningPanel = false
var body: some View {
content
.sheet(isPresented: $showTuningPanel) {
TuningPanel(values: $animationValues)
}
.onShake { showTuningPanel.toggle() }
}
#endif
URL Parameter
const showDevTools = new URLSearchParams(window.location.search).has('debug');
Leva Quick Reference (React)
Leva is the recommended library for React projects:
import { useControls, folder, button } from 'leva';
const Component = () => {
const values = useControls({
// Grouped parameters
animation: folder({
duration: { value: 300, min: 0, max: 2000, step: 10 },
easing: { value: 'easeOut', options: ['linear', 'easeIn', 'easeOut', 'easeInOut'] },
delay: { value: 0, min: 0, max: 1000, step: 10 },
}),
layout: folder({
padding: { value: 16, min: 0, max: 64, step: 1 },
gap: { value: 8, min: 0, max: 32, step: 1 },
borderRadius: { value: 8, min: 0, max: 32, step: 1 },
}),
colors: folder({
background: '#1a1a2e',
foreground: '#eee',
accent: { value: '#6366f1', label: 'Accent Color' },
}),
// Actions
'Copy for LLM': button(() => exportForLLM(values)),
'Reset All': button(() => resetToDefaults()),
});
return <div style={{ padding: values.padding, gap: values.gap }}>...</div>;
};
Export Format Specification
The LLM export should produce markdown that another Claude instance can immediately act on:
## Tuned Parameters for [ComponentName]
I've dialed in these values for the [animation/layout/etc.]:
### Final Values
```typescript
const config = {
duration: 450,
easing: [0.32, 0.72, 0, 1],
stiffness: 180,
damping: 24,
};
Changes from Defaults
| Parameter | Default | Tuned | Reason |
|---|---|---|---|
| duration | 300ms | 450ms | Slower feels more premium |
| damping | 20 | 24 | Reduces overshoot |
Apply These Values
Update the component at src/components/Card.tsx:42 with the config above.
## Additional Resources
### Reference Files
- **`references/platform-libraries.md`** - Detailed setup and API for each platform library
- **`references/parameter-categories.md`** - Exhaustive list of parameters by domain
### Example Files
- **`examples/react-leva-animation.tsx`** - Complete animation tuning panel
- **`examples/export-format.md`** - Full LLM export template