playwright-mcp/src/themes/mcpToolbarTemplate.ts
Ryan Malloy 6120506e91
Some checks failed
CI / test (ubuntu-latest) (push) Has been cancelled
CI / test (windows-latest) (push) Has been cancelled
CI / test_docker (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (macos-latest) (push) Has been cancelled
feat: comprehensive MCP client debug enhancements and voice collaboration
Adds revolutionary features for MCP client identification and browser automation:

MCP Client Debug System:
- Floating pill toolbar with client identification and session info
- Theme system with 5 built-in themes (minimal, corporate, hacker, glass, high-contrast)
- Custom theme creation API with CSS variable overrides
- Cross-site validation ensuring toolbar persists across navigation
- Session-based injection with persistence across page loads

Voice Collaboration (Prototype):
- Web Speech API integration for conversational browser automation
- Bidirectional voice communication between AI and user
- Real-time voice guidance during automation tasks
- Documented architecture and future development roadmap

Code Injection Enhancements:
- Model collaboration API for notify, prompt, and inspector functions
- Auto-injection and persistence options
- Toolbar integration with code injection system

Documentation:
- Comprehensive technical achievement documentation
- Voice collaboration architecture and implementation guide
- Theme system integration documentation
- Tool annotation templates for consistency

This represents a major advancement in browser automation UX, enabling
unprecedented visibility and interaction patterns for MCP clients.
2025-11-14 21:36:08 -07:00

562 lines
14 KiB
TypeScript

/**
* MCP Toolbar Semantic HTML Template System
* Professional, accessible HTML structure with no hardcoded styling
*/
export interface McpToolbarConfig {
projectName: string;
sessionId: string;
clientInfo: string;
startTime: number;
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
minimized: boolean;
showDetails: boolean;
themeId: string;
opacity: number;
}
export interface McpToolbarState {
isMinimized: boolean;
isDragging: boolean;
position: { x: number; y: number };
uptime: string;
hostname: string;
}
/**
* Generate semantic HTML structure for MCP toolbar
* Uses BEM methodology for CSS classes and proper ARIA attributes
*/
export function generateToolbarHTML(config: McpToolbarConfig, state: McpToolbarState): string {
const shortSessionId = config.sessionId.substring(0, 8);
return `
<div
class="mcp-toolbar"
data-theme="${config.themeId}"
data-position="${config.position}"
data-minimized="${state.isMinimized}"
data-dragging="${state.isDragging}"
role="toolbar"
aria-label="MCP Client Identification Toolbar for ${config.projectName}"
tabindex="0"
style="opacity: ${config.opacity}"
>
<div class="mcp-toolbar__container">
<header class="mcp-toolbar__header">
<div class="mcp-toolbar__status">
<div
class="mcp-toolbar__status-indicator"
aria-label="Active MCP session"
title="MCP session is active"
></div>
<div class="mcp-toolbar__project-info">
<h1 class="mcp-toolbar__project-name">${escapeHTML(config.projectName)}</h1>
${!state.isMinimized ? `
<span class="mcp-toolbar__session-badge" title="Session ID: ${config.sessionId}">
${shortSessionId}
</span>
` : ''}
</div>
</div>
<div class="mcp-toolbar__controls">
<button
class="mcp-toolbar__toggle-btn"
aria-expanded="${!state.isMinimized}"
aria-controls="mcp-toolbar-details"
title="${state.isMinimized ? 'Expand details' : 'Minimize toolbar'}"
data-action="toggle"
>
<span class="mcp-toolbar__toggle-icon" aria-hidden="true">
${state.isMinimized ? '⊞' : '⊟'}
</span>
<span class="sr-only">
${state.isMinimized ? 'Expand toolbar details' : 'Minimize toolbar'}
</span>
</button>
</div>
</header>
${!state.isMinimized && config.showDetails ? `
<section
class="mcp-toolbar__details"
id="mcp-toolbar-details"
aria-labelledby="mcp-toolbar-details-heading"
>
<h2 id="mcp-toolbar-details-heading" class="sr-only">Session Details</h2>
<dl class="mcp-toolbar__details-list">
<div class="mcp-toolbar__detail-item">
<dt class="mcp-toolbar__detail-label">Session</dt>
<dd class="mcp-toolbar__detail-value mcp-toolbar__detail-value--mono">
${shortSessionId}
</dd>
</div>
<div class="mcp-toolbar__detail-item">
<dt class="mcp-toolbar__detail-label">Client</dt>
<dd class="mcp-toolbar__detail-value mcp-toolbar__detail-value--mono">
${escapeHTML(config.clientInfo)}
</dd>
</div>
<div class="mcp-toolbar__detail-item">
<dt class="mcp-toolbar__detail-label">Uptime</dt>
<dd class="mcp-toolbar__detail-value mcp-toolbar__detail-value--mono">
${state.uptime}
</dd>
</div>
<div class="mcp-toolbar__detail-item">
<dt class="mcp-toolbar__detail-label">Host</dt>
<dd class="mcp-toolbar__detail-value mcp-toolbar__detail-value--mono">
${escapeHTML(state.hostname)}
</dd>
</div>
</dl>
</section>
` : ''}
</div>
</div>
`;
}
/**
* Generate base CSS framework with CSS custom properties
* This provides the complete styling foundation that works with any theme
*/
export function generateToolbarCSS(): string {
return `
/* =========================================
MCP Toolbar Base Styles
========================================= */
.mcp-toolbar {
/* Layout & Positioning */
position: fixed;
z-index: 2147483647;
/* Base Dimensions */
min-width: var(--mcp-toolbar-min-width);
max-width: var(--mcp-toolbar-max-width);
/* Visual Foundation */
background: var(--mcp-surface);
color: var(--mcp-text-primary);
border: 1px solid var(--mcp-border);
border-radius: var(--mcp-border-radius-md);
box-shadow: var(--mcp-shadow-lg);
/* Backdrop Effects */
backdrop-filter: blur(var(--mcp-backdrop-blur));
-webkit-backdrop-filter: blur(var(--mcp-backdrop-blur));
/* Typography */
font-family: var(--mcp-font-family);
font-size: var(--mcp-font-size-sm);
line-height: 1.4;
/* Interaction */
cursor: grab;
user-select: none;
/* Transitions */
transition:
transform var(--mcp-transition-fast),
box-shadow var(--mcp-transition-fast),
opacity var(--mcp-transition-fast);
}
/* Position Variants */
.mcp-toolbar[data-position="top-left"] {
top: var(--mcp-spacing-lg);
left: var(--mcp-spacing-lg);
}
.mcp-toolbar[data-position="top-right"] {
top: var(--mcp-spacing-lg);
right: var(--mcp-spacing-lg);
}
.mcp-toolbar[data-position="bottom-left"] {
bottom: var(--mcp-spacing-lg);
left: var(--mcp-spacing-lg);
}
.mcp-toolbar[data-position="bottom-right"] {
bottom: var(--mcp-spacing-lg);
right: var(--mcp-spacing-lg);
}
/* Minimized State */
.mcp-toolbar[data-minimized="true"] {
border-radius: var(--mcp-border-radius-pill);
min-width: auto;
max-width: 280px;
}
/* Dragging State */
.mcp-toolbar[data-dragging="true"] {
cursor: grabbing;
transform: translateY(0px) !important;
box-shadow: var(--mcp-shadow-xl);
}
/* Hover Enhancement */
.mcp-toolbar:hover {
transform: translateY(-1px);
box-shadow: var(--mcp-shadow-xl);
opacity: 1 !important;
}
.mcp-toolbar:active {
transform: translateY(0px);
}
/* Focus State for Accessibility */
.mcp-toolbar:focus-visible {
outline: 2px solid var(--mcp-border-focus);
outline-offset: 2px;
}
/* =========================================
Container & Layout
========================================= */
.mcp-toolbar__container {
padding: var(--mcp-spacing-md) var(--mcp-spacing-lg);
display: flex;
flex-direction: column;
gap: var(--mcp-spacing-sm);
}
.mcp-toolbar[data-minimized="true"] .mcp-toolbar__container {
padding: var(--mcp-spacing-sm) var(--mcp-spacing-md);
gap: 0;
}
/* =========================================
Header Section
========================================= */
.mcp-toolbar__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--mcp-spacing-sm);
min-height: 24px;
}
.mcp-toolbar__status {
display: flex;
align-items: center;
gap: var(--mcp-spacing-sm);
flex: 1;
min-width: 0; /* Allows text truncation */
}
.mcp-toolbar__status-indicator {
width: 8px;
height: 8px;
border-radius: var(--mcp-border-radius-full);
background: var(--mcp-success);
flex-shrink: 0;
/* Pulse Animation */
box-shadow: 0 0 0 2px color-mix(in srgb, var(--mcp-success) 20%, transparent);
animation: mcp-pulse 2s infinite;
}
@keyframes mcp-pulse {
0%, 100% {
box-shadow: 0 0 0 2px color-mix(in srgb, var(--mcp-success) 20%, transparent);
}
50% {
box-shadow: 0 0 0 4px color-mix(in srgb, var(--mcp-success) 10%, transparent);
}
}
.mcp-toolbar__project-info {
display: flex;
align-items: center;
gap: var(--mcp-spacing-xs);
flex: 1;
min-width: 0;
}
.mcp-toolbar[data-minimized="true"] .mcp-toolbar__project-info {
flex-direction: row;
}
.mcp-toolbar__project-name {
font-size: var(--mcp-font-size-sm);
font-weight: 600;
margin: 0;
color: var(--mcp-text-primary);
/* Text Truncation */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
}
.mcp-toolbar[data-minimized="false"] .mcp-toolbar__project-name {
font-size: var(--mcp-font-size-base);
}
.mcp-toolbar__session-badge {
font-family: var(--mcp-font-family-mono);
font-size: var(--mcp-font-size-xs);
color: var(--mcp-text-secondary);
background: var(--mcp-bg-hover);
padding: 2px var(--mcp-spacing-xs);
border-radius: var(--mcp-border-radius-sm);
flex-shrink: 0;
}
/* =========================================
Controls Section
========================================= */
.mcp-toolbar__controls {
display: flex;
align-items: center;
gap: var(--mcp-spacing-xs);
}
.mcp-toolbar__toggle-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
min-width: 24px; /* Ensure minimum touch target */
background: transparent;
border: none;
border-radius: var(--mcp-border-radius-sm);
color: var(--mcp-text-secondary);
cursor: pointer;
font-size: var(--mcp-font-size-xs);
transition: all var(--mcp-transition-fast);
}
.mcp-toolbar__toggle-btn:hover {
background: var(--mcp-bg-hover);
color: var(--mcp-text-primary);
transform: scale(1.05);
}
.mcp-toolbar__toggle-btn:active {
transform: scale(0.95);
background: var(--mcp-bg-active);
}
.mcp-toolbar__toggle-btn:focus-visible {
outline: 2px solid var(--mcp-border-focus);
outline-offset: 1px;
}
.mcp-toolbar__toggle-icon {
display: block;
line-height: 1;
}
/* =========================================
Details Section
========================================= */
.mcp-toolbar__details {
border-top: 1px solid var(--mcp-border-subtle);
padding-top: var(--mcp-spacing-sm);
margin-top: var(--mcp-spacing-xs);
}
.mcp-toolbar__details-list {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: var(--mcp-spacing-xs);
}
.mcp-toolbar__detail-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--mcp-spacing-sm);
}
.mcp-toolbar__detail-label {
font-size: var(--mcp-font-size-xs);
color: var(--mcp-text-secondary);
font-weight: 400;
margin: 0;
flex-shrink: 0;
}
.mcp-toolbar__detail-value {
font-size: var(--mcp-font-size-xs);
color: var(--mcp-text-primary);
font-weight: 500;
margin: 0;
text-align: right;
/* Allow value to wrap if needed */
word-break: break-all;
min-width: 0;
}
.mcp-toolbar__detail-value--mono {
font-family: var(--mcp-font-family-mono);
}
/* =========================================
Screen Reader & Accessibility
========================================= */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* =========================================
Responsive Design
========================================= */
@media (max-width: 768px) {
.mcp-toolbar {
font-size: var(--mcp-font-size-xs);
min-width: 240px;
max-width: 300px;
}
.mcp-toolbar__container {
padding: var(--mcp-spacing-sm) var(--mcp-spacing-md);
}
.mcp-toolbar__project-name {
font-size: var(--mcp-font-size-sm);
}
.mcp-toolbar[data-minimized="false"] .mcp-toolbar__project-name {
font-size: var(--mcp-font-size-sm);
}
.mcp-toolbar__detail-label,
.mcp-toolbar__detail-value {
font-size: 10px;
}
}
/* =========================================
Reduced Motion Support
========================================= */
@media (prefers-reduced-motion: reduce) {
.mcp-toolbar,
.mcp-toolbar__toggle-btn,
.mcp-toolbar__status-indicator {
animation: none !important;
transition: none !important;
}
.mcp-toolbar:hover {
transform: none !important;
}
}
/* =========================================
High Contrast Support
========================================= */
@media (prefers-contrast: high) {
.mcp-toolbar {
border-width: 2px;
border-style: solid;
}
.mcp-toolbar__toggle-btn:focus-visible {
outline-width: 3px;
}
.mcp-toolbar__status-indicator {
border: 2px solid var(--mcp-text-primary);
}
}
/* =========================================
Dark Mode Support (system level)
========================================= */
@media (prefers-color-scheme: dark) {
.mcp-toolbar[data-theme="auto"] {
/* Themes handle this through CSS variables */
/* This is just a placeholder for system-level overrides */
}
}
`;
}
/**
* Utility function to escape HTML content
*/
function escapeHTML(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Generate the complete toolbar component with theme integration
*/
export function generateCompleteToolbar(config: McpToolbarConfig, themeCSS: string): string {
const formatUptime = (startTime: number): string => {
const uptime = Math.floor((Date.now() - startTime) / 1000);
const hours = Math.floor(uptime / 3600);
const minutes = Math.floor((uptime % 3600) / 60);
const seconds = uptime % 60;
if (hours > 0) return `${hours}h ${minutes}m`;
if (minutes > 0) return `${minutes}m ${seconds}s`;
return `${seconds}s`;
};
const state: McpToolbarState = {
isMinimized: config.minimized,
isDragging: false,
position: { x: 0, y: 0 },
uptime: formatUptime(config.startTime),
hostname: typeof window !== 'undefined' ? (window.location.hostname || 'local') : 'local'
};
const toolbarHTML = generateToolbarHTML(config, state);
const baseCSS = generateToolbarCSS();
return `
<!-- MCP Toolbar Theme Styles -->
<style id="mcp-toolbar-theme-styles">
${themeCSS}
</style>
<!-- MCP Toolbar Base Styles -->
<style id="mcp-toolbar-base-styles">
${baseCSS}
</style>
<!-- MCP Toolbar Component -->
${toolbarHTML}
`;
}