Remove all references to the incorrect `config.watchPlugins: false` requirement that was believed necessary for Headlamp v0.39.0+. Investigation revealed that plugins work correctly with the default `watchPlugins: true` setting. The earlier documentation was based on a misunderstanding of the plugin loading mechanism. Changes: - Remove watchPlugins: false from all YAML configuration examples - Remove warning sections about watchPlugins requirement - Update troubleshooting guides to focus on actual issues - Simplify installation instructions by removing unnecessary config Files updated: - README.md (main installation docs and troubleshooting table) - docs/DEPLOYMENT.md - docs/TROUBLESHOOTING.md - docs/getting-started/* (quick-start, installation, prerequisites) - docs/deployment/* (helm, production) - docs/troubleshooting/* (common-issues, README) - Multiple other doc files formatted by prettier This cleanup ensures ArtifactHub and GitHub documentation show correct, simplified installation instructions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
6.6 KiB
ADR-001: Use React Context for State Management
Status: Accepted Date: 2026-02-12 Deciders: Plugin maintainers
Context
The Headlamp Polaris Plugin needs to fetch Polaris audit data once and share it across multiple components:
- Dashboard view (cluster overview)
- Namespaces list view
- Namespace detail view (drawer)
- Inline audit sections on resource detail pages
- App bar score badge
Multiple state management approaches are available: Redux, Zustand, Jotai, Recoil, React Context (built-in), or component props with prop drilling.
Constraints:
- Headlamp plugin environment does not allow adding external dependencies (peer dependencies only)
- Redux, Zustand, Jotai, Recoil are not available in the plugin runtime
- Plugin must work with Headlamp's existing React context (React 17+)
- Bundle size should remain small (<50 KB)
Requirements:
- Share
AuditDataobject across all views without duplicate API calls - Support auto-refresh on user-configurable interval (1-30 minutes)
- Handle loading and error states consistently
- Minimal re-renders (data updates infrequently)
Decision
Use React Context API (built-in, no dependencies) for shared state management.
Implementation:
PolarisDataProviderwraps all plugin routesusePolarisDataContext()hook provides{ data, loading, error, refresh }to consumers- Single fetch shared across all views
- Auto-refresh via
useEffect+ interval timer
Consequences
Positive
- ✅ No additional dependencies - Plugins cannot add external libraries (Headlamp constraint)
- ✅ Simple implementation - Single AuditData object, read-only, no complex mutations
- ✅ Built into React - No learning curve, well-documented, stable API
- ✅ Small bundle impact - 0 KB additional (built-in feature)
- ✅ Works with Headlamp - Compatible with Headlamp's React version and plugin system
- ✅ TypeScript support - Full type safety with
React.createContext<T>()
Negative
- ❌ Less powerful for complex state - No built-in middleware, time-travel debugging, or DevTools
- ❌ Potential for unnecessary re-renders - All consumers re-render on context update
- Mitigated by: Data updates every 5-30 minutes (low frequency), memoization not needed
- ❌ No built-in async handling - Must implement loading/error states manually
- Mitigated by: Simple
useState+useEffectpattern sufficient
- Mitigated by: Simple
Neutral
- Performance is excellent for this use case (infrequent updates, small consumer count)
- Context providers work well for read-only or mostly-read state
- Standard React pattern, familiar to contributors
Alternatives Considered
Option 1: Redux
Pros:
- Powerful state management with middleware
- Excellent DevTools for debugging
- Time-travel debugging
- Well-established patterns
Cons:
- Redux is not available as a peer dependency in Headlamp plugins
- Massive overkill for single AuditData object
- Adds significant bundle size (~10-15 KB)
- Requires additional boilerplate (actions, reducers, store)
Decision: Rejected (dependency not available, too heavy)
Option 2: Zustand
Pros:
- Lightweight (~1 KB)
- Simple API similar to
useState - No provider boilerplate
Cons:
- External peer dependency (not available in plugin runtime)
- Still adds bundle size
- Unnecessary for read-only state
Decision: Rejected (dependency not available)
Option 3: Component Props (Prop Drilling)
Pros:
- No dependencies
- Explicit data flow
- TypeScript tracks prop types
Cons:
- Prop drilling through 5+ component layers (index.tsx → route → view → subcomponent)
- Duplicate fetches if not carefully managed
- Refactoring nightmare if component tree changes
- Each route would need its own fetch logic
Decision: Rejected (poor code organization, maintenance burden)
Option 4: Global Variable / Module State
Pros:
- Simple to implement
- No React dependencies
Cons:
- No reactivity (components don't re-render on data change)
- No built-in loading/error handling
- Breaks React's declarative model
- Testing difficulties (global mutable state)
Decision: Rejected (not idiomatic React, no reactivity)
Implementation Details
Context Definition:
interface PolarisDataContextValue {
data: AuditData | null;
loading: boolean;
error: string | null;
refresh: () => void;
}
const PolarisDataContext = React.createContext<PolarisDataContextValue | undefined>(undefined);
Provider Implementation:
export function PolarisDataProvider({ children }: { children: React.ReactNode }) {
const [data, setData] = useState<AuditData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [refreshKey, setRefreshKey] = useState(0);
const refresh = useCallback(() => {
setRefreshKey(k => k + 1);
}, []);
useEffect(() => {
// Fetch logic here
// Auto-refresh on interval
}, [refreshKey]);
return (
<PolarisDataContext.Provider value={{ data, loading, error, refresh }}>
{children}
</PolarisDataContext.Provider>
);
}
Consumer Hook:
export function usePolarisDataContext() {
const context = useContext(PolarisDataContext);
if (!context) {
throw new Error('usePolarisDataContext must be used within PolarisDataProvider');
}
return context;
}
Validation Criteria
Success Metrics:
- ✅ All views share single fetch (verified via network tab - one request per refresh)
- ✅ No duplicate API calls (verified via Kubernetes audit logs)
- ✅ Auto-refresh works correctly (5-30 minute intervals)
- ✅ Loading states consistent across views
- ✅ Error handling consistent across views
- ✅ Bundle size remains <50 KB (currently ~27 KB)
Tested Scenarios:
- ✅ Initial load with loading spinner
- ✅ Error handling (403, 404, network errors)
- ✅ Manual refresh via button
- ✅ Auto-refresh interval (configurable via settings)
- ✅ Multiple views consuming same data (no duplicate fetches)
- ✅ Navigation between routes (data persists)
References
Revision History
| Date | Author | Change |
|---|---|---|
| 2026-02-12 | Plugin Team | Initial decision |