Code quality visualization panels including QualityHexagon components
npm install @principal-ade/code-quality-panelsA starter template for building panel extensions compatible with @principal-ade/panel-framework-core. This template follows the Panel Extension Store Specification V2 and provides everything you need to create, build, and publish custom panels.
Panel extensions are React components distributed via NPM that can be dynamically loaded into panel-compatible host applications. They provide a standardized way to extend application functionality through a plugin-like architecture.
- NPM Distribution: Published and installed like any NPM package
- Multi-Panel Support: Single package can export multiple related panels
- Framework Integration: Full access to host application context, actions, and events
- Type Safety: Complete TypeScript support with comprehensive type definitions
- Dependency Sharing: Shared dependencies (React, ReactDOM) provided by host
- Self-Contained: Unique dependencies bundled within the panel
``bashClone the starter
git clone https://github.com/your-org/panel-starter.git my-panel-extension
cd my-panel-extension
$3
Update
package.json with your information:`json
{
"name": "@your-org/your-panel-name",
"description": "Your panel description",
"author": "Your Name",
"keywords": ["panel-extension"],
"repository": {
"url": "git+https://github.com/your-org/your-panel-name.git"
}
}
`$3
Edit
src/panels/ExamplePanel.tsx or create new panel components:`tsx
import React from "react";
import type { PanelComponentProps } from "../types";export const MyPanel: React.FC = ({
context,
actions,
events,
}) => {
return (
My Custom Panel
Repository: {context.repositoryPath}
);
};
`$3
Update
src/index.tsx to export your panel definitions:`tsx
import { MyPanel } from "./panels/MyPanel";export const panels = [
{
id: "your-org.my-panel",
name: "My Panel",
icon: "š",
description: "My custom panel",
component: MyPanel,
},
];
`$3
`bash
Start Storybook for interactive development
bun run storybookBuild Storybook for deployment
bun run build-storybook
`Storybook will open at
http://localhost:6006 with:- Interactive component documentation
- Multiple panel states and examples
- Live prop editing
- Code snippets
$3
`bash
Development mode (watch for changes)
bun run devBuild for production
bun run buildType checking
bun run typecheckLinting
bun run lint
`Project Structure
`
panel-starter/
āāā src/
ā āāā panels/
ā ā āāā ExamplePanel.tsx # Your panel components
ā ā āāā ExamplePanel.stories.tsx # Storybook stories
ā āāā types/
ā ā āāā index.ts # TypeScript type definitions
ā āāā mocks/
ā ā āāā panelContext.tsx # Mock providers for Storybook
ā āāā Introduction.mdx # Storybook introduction
ā āāā index.tsx # Main entry - export panels array
āāā .storybook/
ā āāā main.ts # Storybook configuration
ā āāā preview.ts # Storybook preview config
āāā dist/
ā āāā panels.bundle.js # Built output (generated)
āāā package.json # Package configuration
āāā tsconfig.json # TypeScript config
āāā vite.config.ts # Build configuration
āāā eslint.config.js # Linting rules
āāā README.md # This file
`Panel Component API
$3
Every panel component receives these props:
`typescript
interface PanelComponentProps {
// Access to shared data and state
context: PanelContextValue; // Actions for host interaction
actions: PanelActions;
// Event system for inter-panel communication
events: PanelEventEmitter;
}
`$3
Access repository data and state:
`tsx
const { context } = props;// Repository information
context.repositoryPath; // Current repository path
context.repository; // Repository metadata
// Data slices
context.gitStatus; // Git status information
context.fileTree; // File tree structure
context.markdownFiles; // Markdown files list
// State management
context.loading; // Loading state
context.refresh(); // Refresh data
context.hasSlice("git"); // Check slice availability
`$3
Interact with the host application:
`tsx
const { actions } = props;// File operations
actions.openFile?.("path/to/file.ts");
actions.openGitDiff?.("path/to/file.ts", "unstaged");
// Navigation
actions.navigateToPanel?.("panel-id");
// Notifications
actions.notifyPanels?.(event);
`$3
Subscribe to and emit panel events:
`tsx
const { events } = props;// Subscribe to events
useEffect(() => {
const unsubscribe = events.on("file:opened", (event) => {
console.log("File opened:", event.payload);
});
return unsubscribe; // Cleanup
}, [events]);
// Emit events
events.emit({
type: "custom:event",
source: "my-panel",
timestamp: Date.now(),
payload: { data: "value" },
});
`Panel Definition
Each panel must be defined with metadata:
`typescript
interface PanelDefinition {
id: string; // Unique ID (e.g., 'org.panel-name')
name: string; // Display name
icon?: string; // Icon (emoji or URL)
version?: string; // Version (defaults to package.json)
author?: string; // Author (defaults to package.json)
description?: string; // Short description
component: React.FC; // The panel component // Optional lifecycle hooks
onMount?: (context) => void | Promise;
onUnmount?: (context) => void | Promise;
onDataChange?: (slice, data) => void;
}
`Lifecycle Hooks
$3
Called for individual panels:
`typescript
{
id: 'my-panel',
component: MyPanel, onMount: async (context) => {
console.log('Panel mounted');
if (context.hasSlice('git')) {
await context.refresh();
}
},
onUnmount: async (context) => {
console.log('Panel unmounting');
// Cleanup logic
},
onDataChange: (slice, data) => {
console.log(
Data changed: ${slice}, data);
},
}
`$3
Called once for the entire package:
`typescript
export const onPackageLoad = async () => {
console.log("Package loaded");
// Initialize shared resources
};export const onPackageUnload = async () => {
console.log("Package unloading");
// Cleanup shared resources
};
`Building and Publishing
$3
The build process (via Vite) automatically:
- Externalizes React and ReactDOM (provided by host)
- Bundles all other dependencies
- Generates TypeScript declarations
- Creates source maps
- Outputs to
dist/panels.bundle.js$3
Link your panel locally for testing:
`bash
In your panel directory
bun run build
bun linkIn your host application
bun link @your-org/your-panel-name
`$3
`bash
Build the package
bun run buildVerify the output
ls -la dist/Publish to NPM
npm publish --access public
`$3
`bash
In the host application
npm install @your-org/your-panel-name
`The host application will automatically discover your panel by the
panel-extension keyword in package.json.Best Practices
$3
Use reverse domain notation for panel IDs:
`typescript
id: "com.company.feature-panel"; // ā
Good
id: "my-panel"; // ā Bad (collision risk)
`$3
Always handle errors gracefully:
`tsx
const [error, setError] = useState(null);useEffect(() => {
const loadData = async () => {
try {
if (!context.hasSlice("git")) {
throw new Error("Git data not available");
}
// Use data...
} catch (err) {
setError(err);
}
};
loadData();
}, [context]);
if (error) {
return
Error: {error.message};
}
`$3
Show loading indicators:
`tsx
if (context.loading || context.isSliceLoading("git")) {
return Loading...;
}
`$3
Always unsubscribe from events:
`tsx
useEffect(() => {
const unsubscribe = events.on("event:type", handler);
return unsubscribe; // Cleanup on unmount
}, [events]);
`$3
Use provided types for type safety:
`tsx
import type { PanelComponentProps, GitStatus } from "./types";const MyPanel: React.FC = ({ context }) => {
const gitStatus: GitStatus = context.gitStatus;
// ...
};
`Available Data Slices
Panels can access these data slices from the host:
| Slice | Type | Description |
| ---------- | ---------------- | ---------------------------- |
|
git | GitStatus | Git repository status |
| markdown | MarkdownFile[] | Markdown files in repository |
| fileTree | FileTree | File system tree structure |
| packages | PackageLayer[] | Package dependencies |
| quality | QualityMetrics | Code quality metrics |Check availability before use:
`tsx
if (context.hasSlice("git") && !context.isSliceLoading("git")) {
// Use git data
}
`Event Types
Standard panel events:
| Event | Description | Payload |
| -------------------- | ------------------ | ---------------------- |
|
file:opened | File was opened | { filePath: string } |
| file:saved | File was saved | { filePath: string } |
| file:deleted | File was deleted | { filePath: string } |
| git:status-changed | Git status changed | GitStatus |
| git:commit | Git commit made | { hash: string } |
| git:branch-changed | Branch changed | { branch: string } |
| panel:focus | Panel gained focus | { panelId: string } |
| panel:blur | Panel lost focus | { panelId: string } |
| data:refresh | Data was refreshed | { slices: string[] } |Dependencies
$3
These are provided by the host application:
-
react >= 19.0.0
- react-dom >= 19.0.0$3
-
@principal-ade/panel-framework-core - For advanced panel features$3
Include any libraries unique to your panel:
`json
{
"dependencies": {
"lodash": "^4.17.21",
"date-fns": "^2.29.0",
"your-custom-lib": "^1.0.0"
}
}
`These will be bundled into your panel output.
Troubleshooting
$3
Ensure
package.json has:`json
{
"keywords": ["panel-extension"],
"main": "dist/panels.bundle.js"
}
`$3
Check that peer dependencies are externalized in
vite.config.ts:`typescript
external: ["react", "react-dom"];
`$3
Ensure TypeScript can find types:
`bash
bun run typecheck
`$3
Check browser console and ensure:
- Panel ID is unique
- Required exports are present (
panels` array)- Panel Extension Store Specification V2
- Panel Framework Core
- Example Implementations
MIT Ā© Your Name
Contributions welcome! Please read the contributing guidelines first.
For issues and questions: