diff --git a/capabilities/testdrive-jsui/js/controls/contents-control.js b/capabilities/testdrive-jsui/js/controls/contents-control.js index b4351052..db5afe89 100644 --- a/capabilities/testdrive-jsui/js/controls/contents-control.js +++ b/capabilities/testdrive-jsui/js/controls/contents-control.js @@ -256,33 +256,41 @@ class ContentsControl extends ControlBase { * Build the control content * Override of base class method to provide contents-specific functionality */ - buildContent() { + + /** + * Generate contents control content (called by base class buildContent) + */ + generateContent() { + // Extract headings first + this.extractHeadings(); + return this.safeOperation(() => { - // Extract headings on first build - this.extractHeadings(); + return this.generateContentsHTML(); + }, 'Error generating contents', 'generateContent'); + } - // Generate and set content - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = this.generateContentsHTML(); + /** + * Override buildContent to add control reference and auto-refresh + */ + buildContent() { + super.buildContent(); - // Store reference to this control for onclick handlers - this.element.contentsControl = this; + // Store reference to this control for onclick handlers + if (this.element) { + this.element.contentsControl = this; + } + + // Set up auto-refresh for dynamic content + if (this.updateInterval) { + clearInterval(this.updateInterval); + } + + this.updateInterval = setInterval(() => { + const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length; + if (currentHeadingCount !== this.headings.length) { + this.refreshContents(); } - - // Set up auto-refresh for dynamic content - if (this.updateInterval) { - clearInterval(this.updateInterval); - } - - this.updateInterval = setInterval(() => { - const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length; - if (currentHeadingCount !== this.headings.length) { - this.refreshContents(); - } - }, 5000); // Check every 5 seconds - - }, null, 'buildContent'); + }, 5000); // Check every 5 seconds } /** diff --git a/capabilities/testdrive-jsui/js/controls/control-base.js b/capabilities/testdrive-jsui/js/controls/control-base.js index 7b42d94a..f6b62d50 100644 --- a/capabilities/testdrive-jsui/js/controls/control-base.js +++ b/capabilities/testdrive-jsui/js/controls/control-base.js @@ -260,6 +260,9 @@ class ControlBase { // Style expanded panel panel.style.cssText = ` + position: relative; + display: flex; + flex-direction: column; background: rgba(248, 249, 250, 0.95); border: 1px solid #dee2e6; border-radius: 8px; @@ -267,6 +270,10 @@ class ControlBase { backdrop-filter: blur(8px); min-width: 300px; min-height: 200px; + max-height: calc(100vh - 40px); + width: auto; + height: auto; + overflow: hidden; `; // Style header @@ -281,6 +288,22 @@ class ControlBase { border-bottom: 1px solid #dee2e6; cursor: move; user-select: none; + flex-shrink: 0; + min-height: 40px; + border-radius: 7px 7px 0 0; + margin: -1px -1px 0 -1px; + `; + } + + // Style content area container + const contentArea = this.element.querySelector('.control-content'); + if (contentArea) { + contentArea.style.cssText = ` + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + min-height: 0; `; } @@ -512,15 +535,16 @@ class ControlBase { resizeHandle.style.cssText = ` position: absolute; bottom: 4px; - right: 4px; - width: 8px; - height: 8px; + right: 20px; + width: 12px; + height: 12px; cursor: se-resize; - font-size: 8px; + font-size: 10px; line-height: 1; user-select: none; color: #999; background: transparent; + z-index: 10; `; // Add to the expanded panel @@ -636,14 +660,55 @@ class ControlBase { /** * Build the control content (to be overridden by subclasses) */ + /** + * Build content with consistent styling - calls subclass generateContent() + */ buildContent() { - // Default implementation - subclasses should override this const content = this.element?.querySelector('.control-content'); if (content) { - content.innerHTML = this.config.defaultContent; + // Get content from subclass + const innerContent = this.generateContent ? this.generateContent() : this.config.defaultContent; + + // Apply consistent container styling + content.innerHTML = ` +
${this.config.title}
+ +
+
+ ${innerContent} +
+
+ `; } } + /** + * Generate content - subclasses should override this method + * @returns {string} HTML content for the panel body + */ + generateContent() { + return this.config.defaultContent || `

Panel content goes here...

`; + } + /** * Show the control */ diff --git a/capabilities/testdrive-jsui/js/controls/debug-control.js b/capabilities/testdrive-jsui/js/controls/debug-control.js index dfc63209..c0fb71a4 100644 --- a/capabilities/testdrive-jsui/js/controls/debug-control.js +++ b/capabilities/testdrive-jsui/js/controls/debug-control.js @@ -423,35 +423,36 @@ class DebugControl extends ControlBase { } /** - * Build the control content - * Override of base class method to provide debug-specific functionality + * Generate debug control content (called by base class buildContent) + */ + generateContent() { + return this.safeOperation(() => { + return ` + ${this.generateSystemInfoHTML()} + ${this.generatePerformanceHTML()} + ${this.generateFilterControlsHTML()} + ${this.generateMessagesHTML()} + ${this.generateControlButtonsHTML()} + +
+ Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} | + Filter: ${this.messageFilter.toUpperCase()} | + Messages: ${this.getFilteredMessages().length}/${this.messages.length} +
+ `; + }, 'Error generating debug content', 'generateContent'); + } + + /** + * Override buildContent to add control reference */ buildContent() { - return this.safeOperation(() => { - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = ` -
-
Debug Information
+ super.buildContent(); - ${this.generateSystemInfoHTML()} - ${this.generatePerformanceHTML()} - ${this.generateFilterControlsHTML()} - ${this.generateMessagesHTML()} - ${this.generateControlButtonsHTML()} - -
- Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} | - Filter: ${this.messageFilter.toUpperCase()} | - Messages: ${this.getFilteredMessages().length}/${this.messages.length} -
-
- `; - - // Store reference to this control for onclick handlers - this.element.debugControl = this; - } - }, null, 'buildContent'); + // Store reference to this control for onclick handlers + if (this.element) { + this.element.debugControl = this; + } } /** diff --git a/capabilities/testdrive-jsui/js/controls/edit-control.js b/capabilities/testdrive-jsui/js/controls/edit-control.js index b62dacf0..1f36891f 100644 --- a/capabilities/testdrive-jsui/js/controls/edit-control.js +++ b/capabilities/testdrive-jsui/js/controls/edit-control.js @@ -67,10 +67,7 @@ class EditControl extends ControlBase { generateEditToolsHTML() { return this.safeOperation(() => { return ` -
-
Edit Tools
- - +
Document Actions
@@ -180,7 +177,6 @@ class EditControl extends ControlBase { ${this.unsavedChanges ? '
⚠️ Unsaved changes
' : ''}
- `; }, '

Error generating edit tools

', 'generateEditToolsHTML'); @@ -539,16 +535,25 @@ class EditControl extends ControlBase { * Build the control content * Override of base class method to provide edit-specific functionality */ - buildContent() { + /** + * Generate edit control content (called by base class buildContent) + */ + generateContent() { return this.safeOperation(() => { - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = this.generateEditToolsHTML(); + return this.generateEditToolsHTML(); + }, 'Error generating edit content', 'generateContent'); + } - // Store reference to this control for onclick handlers - this.element.editControl = this; - } - }, null, 'buildContent'); + /** + * Override buildContent to add control reference + */ + buildContent() { + super.buildContent(); + + // Store reference to this control for onclick handlers + if (this.element) { + this.element.editControl = this; + } } /** diff --git a/capabilities/testdrive-jsui/js/controls/status-control.js b/capabilities/testdrive-jsui/js/controls/status-control.js index 934a0efd..4f1ae60c 100644 --- a/capabilities/testdrive-jsui/js/controls/status-control.js +++ b/capabilities/testdrive-jsui/js/controls/status-control.js @@ -131,10 +131,7 @@ class StatusControl extends ControlBase { const formatNumber = (num) => num.toLocaleString(); return ` -
-
Document Statistics
- -
+
Words:
${formatNumber(this.stats.words)} @@ -206,7 +203,6 @@ class StatusControl extends ControlBase { Updated: ${new Date(this.lastUpdateTime).toLocaleTimeString()}
` : ''} -
`; }, '

Error displaying statistics

', 'formatStatistics'); @@ -322,30 +318,37 @@ class StatusControl extends ControlBase { * Build the control content * Override of base class method to provide status-specific functionality */ - buildContent() { + /** + * Generate status control content (called by base class buildContent) + */ + generateContent() { + // Analyze document first + this.analyzeDocument(); + return this.safeOperation(() => { - // Analyze document first - this.analyzeDocument(); + return this.formatStatistics(); + }, 'Error generating status content', 'generateContent'); + } - // Generate and set content - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = this.formatStatistics(); + /** + * Override buildContent to add control reference and auto-refresh + */ + buildContent() { + super.buildContent(); - // Store reference to this control for onclick handlers - this.element.statusControl = this; - } + // Store reference to this control for onclick handlers + if (this.element) { + this.element.statusControl = this; + } - // Set up auto-refresh for dynamic content - if (this.updateInterval) { - clearInterval(this.updateInterval); - } + // Set up auto-refresh for dynamic content + if (this.updateInterval) { + clearInterval(this.updateInterval); + } - this.updateInterval = setInterval(() => { - this.refreshStats(); - }, 10000); // Update every 10 seconds - - }, null, 'buildContent'); + this.updateInterval = setInterval(() => { + this.refreshStats(); + }, 10000); // Update every 10 seconds } /** diff --git a/testdrive-jsui/static/js/controls/contents-control.js b/testdrive-jsui/static/js/controls/contents-control.js index b4351052..db5afe89 100644 --- a/testdrive-jsui/static/js/controls/contents-control.js +++ b/testdrive-jsui/static/js/controls/contents-control.js @@ -256,33 +256,41 @@ class ContentsControl extends ControlBase { * Build the control content * Override of base class method to provide contents-specific functionality */ - buildContent() { + + /** + * Generate contents control content (called by base class buildContent) + */ + generateContent() { + // Extract headings first + this.extractHeadings(); + return this.safeOperation(() => { - // Extract headings on first build - this.extractHeadings(); + return this.generateContentsHTML(); + }, 'Error generating contents', 'generateContent'); + } - // Generate and set content - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = this.generateContentsHTML(); + /** + * Override buildContent to add control reference and auto-refresh + */ + buildContent() { + super.buildContent(); - // Store reference to this control for onclick handlers - this.element.contentsControl = this; + // Store reference to this control for onclick handlers + if (this.element) { + this.element.contentsControl = this; + } + + // Set up auto-refresh for dynamic content + if (this.updateInterval) { + clearInterval(this.updateInterval); + } + + this.updateInterval = setInterval(() => { + const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length; + if (currentHeadingCount !== this.headings.length) { + this.refreshContents(); } - - // Set up auto-refresh for dynamic content - if (this.updateInterval) { - clearInterval(this.updateInterval); - } - - this.updateInterval = setInterval(() => { - const currentHeadingCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length; - if (currentHeadingCount !== this.headings.length) { - this.refreshContents(); - } - }, 5000); // Check every 5 seconds - - }, null, 'buildContent'); + }, 5000); // Check every 5 seconds } /** diff --git a/testdrive-jsui/static/js/controls/control-base.js b/testdrive-jsui/static/js/controls/control-base.js index 7b42d94a..f6b62d50 100644 --- a/testdrive-jsui/static/js/controls/control-base.js +++ b/testdrive-jsui/static/js/controls/control-base.js @@ -260,6 +260,9 @@ class ControlBase { // Style expanded panel panel.style.cssText = ` + position: relative; + display: flex; + flex-direction: column; background: rgba(248, 249, 250, 0.95); border: 1px solid #dee2e6; border-radius: 8px; @@ -267,6 +270,10 @@ class ControlBase { backdrop-filter: blur(8px); min-width: 300px; min-height: 200px; + max-height: calc(100vh - 40px); + width: auto; + height: auto; + overflow: hidden; `; // Style header @@ -281,6 +288,22 @@ class ControlBase { border-bottom: 1px solid #dee2e6; cursor: move; user-select: none; + flex-shrink: 0; + min-height: 40px; + border-radius: 7px 7px 0 0; + margin: -1px -1px 0 -1px; + `; + } + + // Style content area container + const contentArea = this.element.querySelector('.control-content'); + if (contentArea) { + contentArea.style.cssText = ` + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + min-height: 0; `; } @@ -512,15 +535,16 @@ class ControlBase { resizeHandle.style.cssText = ` position: absolute; bottom: 4px; - right: 4px; - width: 8px; - height: 8px; + right: 20px; + width: 12px; + height: 12px; cursor: se-resize; - font-size: 8px; + font-size: 10px; line-height: 1; user-select: none; color: #999; background: transparent; + z-index: 10; `; // Add to the expanded panel @@ -636,14 +660,55 @@ class ControlBase { /** * Build the control content (to be overridden by subclasses) */ + /** + * Build content with consistent styling - calls subclass generateContent() + */ buildContent() { - // Default implementation - subclasses should override this const content = this.element?.querySelector('.control-content'); if (content) { - content.innerHTML = this.config.defaultContent; + // Get content from subclass + const innerContent = this.generateContent ? this.generateContent() : this.config.defaultContent; + + // Apply consistent container styling + content.innerHTML = ` +
${this.config.title}
+ +
+
+ ${innerContent} +
+
+ `; } } + /** + * Generate content - subclasses should override this method + * @returns {string} HTML content for the panel body + */ + generateContent() { + return this.config.defaultContent || `

Panel content goes here...

`; + } + /** * Show the control */ diff --git a/testdrive-jsui/static/js/controls/debug-control.js b/testdrive-jsui/static/js/controls/debug-control.js index dfc63209..c0fb71a4 100644 --- a/testdrive-jsui/static/js/controls/debug-control.js +++ b/testdrive-jsui/static/js/controls/debug-control.js @@ -423,35 +423,36 @@ class DebugControl extends ControlBase { } /** - * Build the control content - * Override of base class method to provide debug-specific functionality + * Generate debug control content (called by base class buildContent) + */ + generateContent() { + return this.safeOperation(() => { + return ` + ${this.generateSystemInfoHTML()} + ${this.generatePerformanceHTML()} + ${this.generateFilterControlsHTML()} + ${this.generateMessagesHTML()} + ${this.generateControlButtonsHTML()} + +
+ Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} | + Filter: ${this.messageFilter.toUpperCase()} | + Messages: ${this.getFilteredMessages().length}/${this.messages.length} +
+ `; + }, 'Error generating debug content', 'generateContent'); + } + + /** + * Override buildContent to add control reference */ buildContent() { - return this.safeOperation(() => { - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = ` -
-
Debug Information
+ super.buildContent(); - ${this.generateSystemInfoHTML()} - ${this.generatePerformanceHTML()} - ${this.generateFilterControlsHTML()} - ${this.generateMessagesHTML()} - ${this.generateControlButtonsHTML()} - -
- Recording: ${this.isRecording ? '🟢 Active' : '🔴 Paused'} | - Filter: ${this.messageFilter.toUpperCase()} | - Messages: ${this.getFilteredMessages().length}/${this.messages.length} -
-
- `; - - // Store reference to this control for onclick handlers - this.element.debugControl = this; - } - }, null, 'buildContent'); + // Store reference to this control for onclick handlers + if (this.element) { + this.element.debugControl = this; + } } /** diff --git a/testdrive-jsui/static/js/controls/edit-control.js b/testdrive-jsui/static/js/controls/edit-control.js index b62dacf0..1f36891f 100644 --- a/testdrive-jsui/static/js/controls/edit-control.js +++ b/testdrive-jsui/static/js/controls/edit-control.js @@ -67,10 +67,7 @@ class EditControl extends ControlBase { generateEditToolsHTML() { return this.safeOperation(() => { return ` -
-
Edit Tools
- - +
Document Actions
@@ -180,7 +177,6 @@ class EditControl extends ControlBase { ${this.unsavedChanges ? '
⚠️ Unsaved changes
' : ''}
-
`; }, '

Error generating edit tools

', 'generateEditToolsHTML'); @@ -539,16 +535,25 @@ class EditControl extends ControlBase { * Build the control content * Override of base class method to provide edit-specific functionality */ - buildContent() { + /** + * Generate edit control content (called by base class buildContent) + */ + generateContent() { return this.safeOperation(() => { - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = this.generateEditToolsHTML(); + return this.generateEditToolsHTML(); + }, 'Error generating edit content', 'generateContent'); + } - // Store reference to this control for onclick handlers - this.element.editControl = this; - } - }, null, 'buildContent'); + /** + * Override buildContent to add control reference + */ + buildContent() { + super.buildContent(); + + // Store reference to this control for onclick handlers + if (this.element) { + this.element.editControl = this; + } } /** diff --git a/testdrive-jsui/static/js/controls/status-control.js b/testdrive-jsui/static/js/controls/status-control.js index 934a0efd..4f1ae60c 100644 --- a/testdrive-jsui/static/js/controls/status-control.js +++ b/testdrive-jsui/static/js/controls/status-control.js @@ -131,10 +131,7 @@ class StatusControl extends ControlBase { const formatNumber = (num) => num.toLocaleString(); return ` -
-
Document Statistics
- -
+
Words:
${formatNumber(this.stats.words)} @@ -206,7 +203,6 @@ class StatusControl extends ControlBase { Updated: ${new Date(this.lastUpdateTime).toLocaleTimeString()}
` : ''} -
`; }, '

Error displaying statistics

', 'formatStatistics'); @@ -322,30 +318,37 @@ class StatusControl extends ControlBase { * Build the control content * Override of base class method to provide status-specific functionality */ - buildContent() { + /** + * Generate status control content (called by base class buildContent) + */ + generateContent() { + // Analyze document first + this.analyzeDocument(); + return this.safeOperation(() => { - // Analyze document first - this.analyzeDocument(); + return this.formatStatistics(); + }, 'Error generating status content', 'generateContent'); + } - // Generate and set content - const content = this.element?.querySelector('.control-content'); - if (content) { - content.innerHTML = this.formatStatistics(); + /** + * Override buildContent to add control reference and auto-refresh + */ + buildContent() { + super.buildContent(); - // Store reference to this control for onclick handlers - this.element.statusControl = this; - } + // Store reference to this control for onclick handlers + if (this.element) { + this.element.statusControl = this; + } - // Set up auto-refresh for dynamic content - if (this.updateInterval) { - clearInterval(this.updateInterval); - } + // Set up auto-refresh for dynamic content + if (this.updateInterval) { + clearInterval(this.updateInterval); + } - this.updateInterval = setInterval(() => { - this.refreshStats(); - }, 10000); // Update every 10 seconds - - }, null, 'buildContent'); + this.updateInterval = setInterval(() => { + this.refreshStats(); + }, 10000); // Update every 10 seconds } /**