Terminal panel for panel-framework - integrated terminal emulator with industry theming
npm install @industry-theme/terminal-panelA terminal emulator panel for the Panel Framework V2, featuring industry theming and full terminal capabilities.
This panel package provides a single, non-tabbed terminal emulator that integrates with the panel-framework's actions and events APIs. It uses @principal-ade/industry-themed-terminal for consistent styling and xterm.js for terminal emulation.
- Full terminal emulation via xterm.js
- Industry-themed UI with custom header
- WebGL acceleration support
- Unicode 11 support
- Built-in search functionality
- Clickable web links
- Configurable directory scope (repository or workspace)
- Framework-integrated lifecycle management
``bash`
npm install @industry-theme/terminal-panelor
bun install @industry-theme/terminal-panel
`tsx
import { TerminalPanel } from '@industry-theme/terminal-panel';
actions={actions}
events={events}
/>
`
For workspace contexts where the terminal should stay in the workspace directory regardless of repository selection:
`tsx`
actions={actions}
events={events}
terminalScope="workspace"
/>
For repository-scoped terminals that use the current repository path:
`tsx`
actions={actions}
events={events}
terminalScope="repository"
/>
The terminalScope prop controls which directory the terminal uses:
- 'repository' (default): Uses context.currentScope.repository.path, falls back to workspace path if no repository'workspace'
- : Uses context.currentScope.workspace.path
`typescript`
type TerminalScope = 'repository' | 'workspace';
`typescript`
interface TerminalPanelProps {
context: PanelContextValue;
actions: TerminalPanelActions;
events: PanelEventEmitter;
terminalScope?: TerminalScope; // 'repository' | 'workspace'
}
`bashStart Storybook for interactive development
bun run 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: