Skip to content

fix: Restore terminal focus after context menu dismissal (#126)#127

Merged
kshivang merged 10 commits intomasterfrom
dev
Dec 20, 2025
Merged

fix: Restore terminal focus after context menu dismissal (#126)#127
kshivang merged 10 commits intomasterfrom
dev

Conversation

@kshivang
Copy link
Copy Markdown
Owner

Summary

  • Fix terminal keyboard input stopping after context menu dismissal when embedded
  • Add embedded-example module for testing embedded terminal scenarios

Problem

When BossTerm is embedded in a parent application (not standalone), the terminal loses keyboard input after dismissing the context menu. This happens because focus is not explicitly restored to the terminal's FocusRequester after the JPopupMenu closes.

Solution

  1. ContextMenuController.kt: Added PopupMenuListener to track when the popup becomes invisible (by any means - clicking menu item, clicking outside, or pressing Escape)
  2. ProperTerminal.kt: Added LaunchedEffect that monitors menuState.isVisible and restores focus when the context menu closes

This follows the existing focus restoration pattern used for search bar and debug panel close handlers.

New Module: embedded-example

Created a test module to reproduce and verify embedded terminal issues:

  • Sidebar with buttons that can steal focus
  • Toolbar with toggle controls
  • Embedded terminal using EmbeddableTerminal API
  • Status bar showing actions

Run with: ./gradlew :embedded-example:run

Test plan

  • Run embedded-example app
  • Right-click in terminal to open context menu
  • Dismiss by clicking menu item → verify keyboard input works
  • Dismiss by clicking outside menu → verify keyboard input works
  • Dismiss by pressing Escape → verify keyboard input works
  • Click sidebar buttons → click back in terminal → verify focus restored

Fixes #126

🤖 Generated with Claude Code

@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review: Fix Terminal Focus After Context Menu Dismissal

This is an excellent fix for issue #126 with a well-designed test harness. The implementation follows existing patterns and addresses the root cause cleanly.


✅ Strengths

1. Root Cause Analysis

  • Correctly identified that focus restoration was missing for context menu dismissal
  • Follows the established pattern used for search bar and debug panel (lines 1448-1473 in ProperTerminal.kt)

2. Clean Architecture

The two-part solution is elegant:

  • ContextMenuController.kt: Updates state when popup becomes invisible
  • ProperTerminal.kt: Reacts to state change and restores focus

This maintains unidirectional data flow and separation of concerns.

3. Comprehensive Testing Module

The embedded-example module is production-quality:

  • Reproduces the exact embedded scenario from issue
  • Multiple focus-stealing scenarios (sidebar buttons, toolbar)
  • Clear visual feedback (status bar)
  • Reusable for future embedded testing

🔍 Code Quality Observations

ProperTerminal.kt (Lines 1477-1483)

The LaunchedEffect implementation is correct:

  • 50ms delay matches UI event loop timing expectations
  • Condition prevents unnecessary focus requests when menu opens
  • Effect triggers on visibility changes, but guard ensures focus only restored on close

ContextMenuController.kt (Lines 111-119)

What Changed:

  • OLD: Used PopupMenuListener to restore window focus via window.requestFocus()
  • NEW: Updates MenuState when popup becomes invisible, lets ProperTerminal handle focus

Why This Is Better:

  1. Window-level focus ≠ Compose component focus
  2. In embedded scenarios, the window might be shared with other components
  3. State-driven approach is more Compose-idiomatic
  4. The old window focus restoration was fighting with Compose's focus system

🧪 Testing Recommendations

Before Merge:

  1. Embedded Scenario (primary fix target):

    • Run ./gradlew :embedded-example:run
    • Test all dismissal methods: Click menu item, Click outside, Press Escape
    • Verify typing works immediately after each
  2. Standalone Mode (regression test):

    • Run ./gradlew :compose-ui:run
    • Same dismissal tests to ensure no behavior regression
  3. Focus Stealing Recovery:

    • In embedded-example, click sidebar buttons then back in terminal
    • Verify focus restoration works
  4. Rapid Context Menu Operations:

    • Open context menu, press Escape, immediately open again
    • Verify no focus thrashing or double-restoration

🔒 Security & Performance

No security concerns

  • No user input processing, no external resources, pure focus management

No performance concerns

  • 50ms delay is negligible
  • State-driven, no polling
  • LaunchedEffect cancels automatically on recomposition

📋 Minor Suggestions

1. Documentation Enhancement

Consider adding a comment explaining the 50ms delay in ProperTerminal.kt:

// Restore focus to terminal when context menu closes
val contextMenuState by contextMenuController.menuState
LaunchedEffect(contextMenuState.isVisible) {
  if (!contextMenuState.isVisible) {
    // Delay ensures popup dismissal completes before focus restoration
    kotlinx.coroutines.delay(50)
    focusRequester.requestFocus()
  }
}

2. Test Coverage

The embedded-example is excellent for manual testing. Future enhancement (not blocking):

  • Consider adding integration test that programmatically verifies focus restoration
  • Could use Compose UI testing framework with focus assertion

✅ Final Verdict

APPROVED

This PR is ready to merge:

  • ✅ Fixes the root cause correctly
  • ✅ Follows project conventions (see CLAUDE.md)
  • ✅ No breaking changes
  • ✅ Excellent test harness for embedded scenarios
  • ✅ Clean, maintainable code

Impact:

  • Fixes critical UX issue for embedded users (BOSS integration)
  • No impact on standalone users
  • Provides valuable test infrastructure for future embedded work

Recommendation: Merge after manual testing confirms all dismissal scenarios work in both embedded and standalone modes.


Great work @kshivang! The focus restoration pattern consistency across search bar, debug panel, and context menu makes the codebase more maintainable. 🎉

@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review - PR #127: Fix Terminal Focus After Context Menu Dismissal

Summary

This PR effectively fixes issue #126 where terminal input stops working after context menu dismissal in embedded scenarios. The solution is well-designed and follows existing patterns in the codebase.


Strengths

  1. Consistent Pattern Usage

    • The fix follows the exact same pattern used for search bar (lines 1448-1462) and debug panel (lines 1467-1473) focus restoration
    • Uses LaunchedEffect + 50ms delay + focusRequester.requestFocus() consistently
    • This consistency makes the codebase more maintainable
  2. Clean State Management

    • ContextMenuController.kt: The PopupMenuListener properly resets _menuState and currentPopup on dismissal
    • Simplified the listener implementation by removing the complex window focus management that wasn't needed
    • The state reset handles all dismissal scenarios (menu item click, outside click, Escape key)
  3. Excellent Test Module

    • The embedded-example module is well-structured and comprehensive
    • Provides realistic test scenarios: sidebar buttons, toolbar controls, command injection
    • Clear documentation and instructions in comments
    • Good UX with status messages showing what's happening
  4. Single-Click Focus Fix

    • ProperTerminal.kt:924 - Adding focusRequester.requestFocus() to single-click handler is a good catch
    • Previously only double/triple clicks restored focus, now all clicks do
  5. Minimal Changes

    • The fix is surgical - only touches what's necessary
    • Net reduction in ContextMenuController.kt (-12 lines) shows good refactoring

Code Quality Observations

ContextMenuController.kt (lines 112-120)

The PopupMenuListener implementation:

  • Correctly updates state only on popupMenuWillBecomeInvisible
  • The popupMenuCanceled is left empty (it's already handled by WillBecomeInvisible)
  • Minor suggestion: The empty popupMenuCanceled could have a comment explaining why it's not implemented

ProperTerminal.kt (lines 1477-1485)

The focus restoration LaunchedEffect:

  • Proper delegation pattern with by keyword
  • 50ms delay matches other focus restoration patterns
  • Potential edge case: This triggers on every change to isVisible=false, including initial state
    • First render: isVisible is false, triggers focus restoration
    • This is harmless but technically unnecessary
    • Verdict: Current implementation is fine (fails safe, minimal cost)

Embedded Example Module

  • Well-organized Material 3 UI
  • Good separation of concerns (Sidebar, Toolbar, StatusBar components)
  • Helpful test scenarios
  • Enhancement idea (for future): Could add a "Steal Focus" button that explicitly calls requestFocus() on a different component

Security & Performance

  • No security concerns: Pure UI focus management
  • Performance: Minimal overhead
    • LaunchedEffect with state observation is efficient
    • 50ms delay is negligible
    • PopupMenuListener is standard Swing pattern
  • Memory: No leaks detected
    • currentPopup is properly cleared
    • Listener lifecycle tied to popup lifecycle

Testing Considerations

Manual Testing Checklist (from PR description):

  • Right-click, click menu item, verify keyboard input works
  • Right-click, click outside, verify keyboard input works
  • Right-click, press Escape, verify keyboard input works
  • Click sidebar buttons, click terminal, verify focus restored

Suggested Additional Tests:

  1. Rapid menu open/close: Open and close context menu multiple times quickly
  2. Menu while typing: Open menu while actively typing (test state consistency)
  3. Multiple terminals: Test in embedded-example with 2+ terminal instances (future work)
  4. Tab key navigation: Verify Tab key still works after menu dismissal

Documentation


Recommendations

  1. Merge as-is - This is production-ready
  2. Optional follow-up: Add the minor comment improvement in ContextMenuController.kt
  3. Future enhancement: Consider adding automated focus tests (if compose test framework supports it)

Final Verdict

LGTM - Excellent fix that:

  • Solves the reported issue comprehensively
  • Follows existing codebase patterns
  • Includes proper testing infrastructure
  • Has minimal risk and good code quality

The embedded-example module is a great addition that will help prevent future focus-related regressions.


Approval Status: APPROVED

Great work @kshivang! This is exactly the right fix for issue #126.

@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

PR Review: Fix Terminal Focus After Context Menu Dismissal (#126)

Summary

This PR successfully addresses issue #126 by restoring keyboard focus to the terminal after context menu dismissal in embedded scenarios. The implementation is clean, well-documented, and includes a comprehensive testing module.


✅ Code Quality & Best Practices

Excellent Consistency

The focus restoration pattern in ProperTerminal.kt (lines 1477-1485) perfectly mirrors the existing patterns for search bar (1460-1464) and debug panel (1469-1475). This consistency makes the codebase more maintainable.

Clean State Management

The ContextMenuController.kt change (lines 112-120) is much cleaner than the previous implementation:

  • Before: Two separate listeners (popupMenuWillBecomeInvisible and popupMenuCanceled) with redundant window focus logic
  • After: Single state update that resets _menuState and currentPopup, letting the reactive UI handle focus restoration

This separation of concerns is excellent - the controller manages menu state, while the UI component handles focus.

Improved API Design

The change from () -> Unit to (() -> Unit)? for several callbacks in ProperTerminal.kt (lines 117, 123-126) is a good improvement. It allows optional features without forcing callers to provide no-op lambdas.


🎯 Problem-Solution Alignment

The fix directly addresses the root cause identified in #126:

  1. Context menu closes (by any means - click, escape, outside click)
  2. PopupMenuListener.popupMenuWillBecomeInvisible() fires
  3. _menuState.value updates to MenuState() (invisible)
  4. LaunchedEffect detects isVisible = false
  5. Focus restored to terminal after 50ms delay

The 50ms delay is appropriate to let the menu fully dismiss before focus restoration.


🧪 Testing Module - Excellent Addition

The embedded-example module is well-designed:

Strong Points:

  • Realistic embedded scenario with sidebar, toolbar, and status bar
  • Multiple focus-stealing UI elements (buttons) to test focus transitions
  • Commands that send input to terminal (ls, pwd, clear)
  • Clear instructions in status bar and sidebar
  • Good use of Material 3 dark theme consistent with BossTerm

Minor Suggestion:
Consider adding a visual indicator (border/highlight) when the terminal has focus vs. when it doesn't. This would make focus state more obvious during testing.


🔒 Security & Performance

No Security Concerns:

  • No user input sanitization issues
  • No command injection vectors
  • Focus management is purely UI-level

Performance:

  • The LaunchedEffect with delay(50) is lightweight and only triggers on menu state changes
  • No performance regression expected

🐛 Potential Issues

Minor: LaunchedEffect Trigger Frequency

The LaunchedEffect(contextMenuState.isVisible) at line 1479 triggers whenever isVisible changes. Consider the edge case:

  1. Menu opens (isVisible = true) - effect runs but doesn't restore focus (correct)
  2. Menu closes (isVisible = false) - effect runs and restores focus (correct)

This should work fine, but there's a subtle issue: the effect will always run when isVisible transitions from true → false, even if the terminal already has focus. This is harmless but slightly inefficient.

Recommendation (optional): Consider checking isActiveTab before requesting focus:

LaunchedEffect(contextMenuState.isVisible, isActiveTab) {
  if (!contextMenuState.isVisible && isActiveTab) {
    delay(50)
    focusRequester.requestFocus()
  }
}

However, the current implementation is safe and follows the existing pattern, so this is a minor optimization rather than a bug.

Minor: Focus Requester Timing

The single-click handler in ProperTerminal.kt (lines 920-925) now calls focusRequester.requestFocus() immediately after click, while the context menu handler uses a 50ms delay. This inconsistency is fine in practice, but worth noting:

  • Immediate focus: Safe when there's no UI element being dismissed
  • Delayed focus: Necessary when waiting for menu to dismiss

This is correct, just worth documenting the difference.


📝 Documentation & Comments

Good:

  • Clear PR description with problem/solution
  • Test plan with multiple dismissal scenarios
  • Inline comment at line 1478 explaining purpose

Could Improve:

  • Add a comment in ContextMenuController.kt explaining why window focus logic was removed ("Focus restoration is now handled by ProperTerminal's LaunchedEffect")

✅ Test Coverage

Manual Testing Required:
The PR provides an excellent manual test plan. The embedded-example module should be used to verify:

  1. Right-click → click menu item → type works ✓
  2. Right-click → click outside → type works ✓
  3. Right-click → press Escape → type works ✓
  4. Click sidebar button → click terminal → type works ✓

No Unit Tests:
While unit testing focus behavior in Compose Desktop is challenging, consider adding integration tests if the project has a testing framework for UI interactions.


🎉 Overall Assessment

Grade: A-

This is a high-quality PR that:

  • Solves the problem cleanly with minimal code changes
  • Follows existing patterns consistently
  • Includes a comprehensive testing module
  • Improves API design (nullable callbacks)
  • Has good documentation

Recommendation: Approve with minor suggestions

The only suggestions are optional optimizations. The core fix is solid and ready to merge after manual testing confirms the issue is resolved.


🔧 Action Items (Optional)

  1. Ready to merge after manual testing
  2. 💡 Optional: Add isActiveTab check to LaunchedEffect (performance optimization)
  3. 💡 Optional: Add comment in ContextMenuController explaining removed window focus logic
  4. 💡 Optional: Add visual focus indicator to embedded-example for easier testing

Great work! This fix will significantly improve the embedded terminal experience. 🚀

@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review Summary

Overall Assessment: Approved with minor suggestions

This PR effectively solves issue 126 by implementing proper focus restoration after context menu dismissal in embedded scenarios. The solution is well-architected and follows existing patterns in the codebase.


Strengths

  1. Excellent Problem Diagnosis - Clear identification of the root cause
  2. Consistent Pattern - LaunchedEffect approach mirrors existing focus restoration for search bar and debug panel
  3. Comprehensive Testing Module - The embedded-example module is excellent for reproducing and verifying the fix
  4. State Management Cleanup - PopupMenuListener properly resets menuState and clears currentPopup reference

Issues and Concerns

1. Potential Race Condition (High Priority)

Location: ContextMenuController.kt:114-119

The PopupMenuListener callbacks execute on the AWT Event Dispatch Thread, but Compose state updates should ideally happen on the Compose dispatcher to avoid potential race conditions.

Recommendation: Wrap _menuState.value update in SwingUtilities.invokeLater

2. Magic Delay Number (Medium Priority)

Location: ProperTerminal.kt:1483

The 50ms delay appears arbitrary without explanation. Consider documenting why 50ms or making it a named constant.

3. Redundant Focus Request (Low Priority)

Location: ProperTerminal.kt:924

The focus request on mouse press may be redundant with context menu focus restoration logic. If this is for non-context-menu clicks, add a clarifying comment.

4. Type Signature Changes (Low Priority)

Location: ProperTerminal.kt:114-125

Several callbacks changed from non-nullable with default empty lambda to nullable. PR description does not mention this change. Please clarify if necessary for embedded scenario.


Security and Performance

  • No security concerns identified
  • No performance issues
  • No resource leaks

Recommendations Summary

  1. High Priority: Wrap _menuState.value update in SwingUtilities.invokeLater
  2. Medium Priority: Document the 50ms delay or make it a named constant
  3. Low Priority: Clarify redundant focus request on line 924
  4. Low Priority: Explain nullable callback signature changes

Conclusion

This is a solid fix that addresses a real usability issue. The code is clean, follows existing patterns, and includes excellent testing infrastructure. The concerns raised are minor and mostly about polish rather than correctness.

Recommendation: Merge after addressing the AWT threading concern and documenting the delay.

Great work!

@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review - PR #127: Fix terminal focus after context menu dismissal

✅ Overall Assessment

This is a well-implemented fix for issue #126. The solution is clean, follows existing patterns in the codebase, and includes excellent test infrastructure. Ready to merge with minor considerations below.


🎯 Strengths

  1. Correct Root Cause Analysis

    • Properly identified that embedded terminals lose focus after context menu dismissal
    • Solution addresses both Swing (JPopupMenu) and Compose (focus restoration) layers
  2. Consistent Pattern

    • Follows the exact same approach as existing debug panel focus restoration (lines 1473-1479 in ProperTerminal.kt)
    • 50ms delay is reasonable for Swing event loop synchronization
  3. Comprehensive Test Module

    • The embedded-example module is excellent for reproducing focus-related issues
    • Custom context menu items demo is a nice touch
    • Clear instructions in PR description
  4. API Enhancement

    • ContextMenuItem data class is well-documented and simple
    • customContextMenuItems parameter is properly threaded through the call chain
    • enableDebugPanel flag is a sensible addition for embedded scenarios
  5. Backwards Compatibility

    • All new parameters have sensible defaults
    • Nullable callbacks (onNewWindow, onShowSettings, etc.) prevent no-op registrations

⚠️ Considerations & Minor Issues

1. Potential Double Focus Restoration (Low Priority)

// ProperTerminal.kt:1482-1489
LaunchedEffect(contextMenuState.isVisible) {
  if (!contextMenuState.isVisible) {
    kotlinx.coroutines.delay(50)
    focusRequester.requestFocus()
  }
}

Issue: The PopupMenuListener in ContextMenuController.kt (line 114-118) already updates _menuState.value = MenuState(), which triggers this LaunchedEffect. However, this is actually correct behavior - the listener ensures state consistency, and the LaunchedEffect handles focus restoration. No action needed.

2. Unused popupMenuCanceled Handler (Cosmetic)

// ContextMenuController.kt:119
override fun popupMenuCanceled(e: javax.swing.event.PopupMenuEvent?) {}

Suggestion: According to Swing docs, popupMenuCanceled is called before popupMenuWillBecomeInvisible, so the current implementation is correct. However, you could add a comment:

override fun popupMenuCanceled(e: javax.swing.event.PopupMenuEvent?) {
    // No action needed - popupMenuWillBecomeInvisible will handle state reset
}

3. Missing Edge Case Documentation

The PR description mentions "dismiss by clicking menu item, clicking outside, or pressing Escape", but there's one more case: clicking the terminal canvas while menu is open.

Lines 926-928 in ProperTerminal.kt handle this:

isDragging = false
// Ensure focus is on terminal canvas after click
focusRequester.requestFocus()

This is already handled correctly - good catch! Consider adding this to the test plan.

4. Custom Menu Items Separator Logic (Minor)

// ContextMenuController.kt:342-360
val customMenuItems = if (customItems.isNotEmpty()) {
    listOf(
        ContextMenuController.MenuItem(
            id = "separator_custom",
            label = "",
            enabled = false,
            action = {}
        )
    ) + customItems.map { ... }
} else {
    emptyList()
}

Observation: The separator has enabled = false, which will render as a disabled menu item (grayed out) rather than a visual separator (JSeparator). This might be intentional for dark theme styling, but worth verifying visually.


🔒 Security & Performance

No security concerns - Standard focus management, no user input processing
No performance issues - Single listener per popup, minimal overhead
Memory management - currentPopup = null prevents leaks


📋 Test Plan Additions

Your test plan is comprehensive. I'd suggest adding:

  • Click terminal canvas while context menu is open → verify focus restored
  • Open context menu, press Alt+Tab to switch apps, return → verify menu dismissed
  • Rapidly open/close context menu (stress test) → verify no focus thrashing

📝 Minor Code Quality Notes

  1. Nullable Callback Pattern (lines 114-142 in ProperTerminal.kt)
    The change from onNewWindow: () -> Unit = {} to onNewWindow: (() -> Unit)? = null is excellent. Consider doing the same for onCloseTab, onNextTab, etc. for consistency (future enhancement, not blocking).

  2. CLAUDE.md Adherence
    ✅ Commit message follows guidelines (no Co-Authored-By)
    ✅ Targets correct branch (should merge to master)
    ✅ Code follows existing patterns


✅ Recommendation

APPROVE - Ready to merge after confirming the separator rendering looks correct in the embedded example. The fix is solid, well-tested, and follows project conventions.

Great work on the embedded example module - this will be valuable for future UI testing!


Reviewed by: Claude Sonnet 4.5
Review Date: 2025-12-20

@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review: PR #127 - Fix terminal focus after context menu dismissal

Summary

This PR effectively addresses issue #126 by implementing focus restoration after context menu dismissal in embedded terminal scenarios. The solution is well-architected and follows existing patterns in the codebase.


✅ Strengths

1. Clean Architecture

  • Follows existing focus restoration pattern used for search bar and debug panel (ProperTerminal.kt:1473-1478)
  • Uses reactive state management with LaunchedEffect for side effects
  • Properly isolates concerns between menu state tracking and focus restoration

2. Comprehensive Solution

  • Handles all dismissal paths: menu item click, outside click, and Escape key
  • PopupMenuListener in ContextMenuController.kt:112-120 properly updates state on popupMenuWillBecomeInvisible
  • State update (_menuState.value = MenuState()) ensures Compose observes the change

3. Excellent Testing Infrastructure

  • New embedded-example module provides reproducible test environment
  • Clear test scenarios documented in PR description
  • Demonstrates best practices for embedding BossTerm in parent applications

4. API Enhancements

  • ContextMenuItem data class is well-designed with clear documentation
  • Custom context menu items feature adds valuable extensibility
  • Proper separation of concerns with nullable callbacks in ProperTerminal.kt

🔍 Code Quality Observations

1. Duplicate Focus Restoration Logic

The 50ms delay pattern appears in three places:

  • ProperTerminal.kt:1476 (debug panel)
  • ProperTerminal.kt:1486 (context menu)
  • ProperTerminal.kt:927 (click handler)

Suggestion: Consider extracting this to a helper:

private suspend fun restoreFocusDelayed(focusRequester: FocusRequester, delayMs: Long = 50) {
    kotlinx.coroutines.delay(delayMs)
    focusRequester.requestFocus()
}

2. Race Condition Risk (Minor)

ProperTerminal.kt:1482-1489 uses val contextMenuState by contextMenuController.menuState which creates a delegated property. The LaunchedEffect will trigger on ANY change to contextMenuState, but only acts when isVisible == false.

Edge case: If the menu state updates multiple times rapidly (e.g., open → close → open → close), multiple coroutines could be launched. While harmless (multiple requestFocus() calls are idempotent), it's worth noting.

Suggestion: Use LaunchedEffect(contextMenuState.isVisible) key instead to only retrigger when visibility actually changes, or add isVisible to the key list.

3. Nullable Callbacks Pattern

ProperTerminal.kt:117-143 converts nullable callbacks to empty lambdas:

onNewTab: (() -> Unit)? = null,
// Later:
onNewTab = onNewTab ?: {},

This is consistent, but the context menu functions (onShowDebug, onShowSettings, onNewWindow) remain nullable and are checked at call sites. Consider standardizing the approach.


🐛 Potential Issues

1. Focus Restoration in Multi-Window Scenarios

If multiple BossTerm windows are open and a context menu is dismissed in a non-focused window, focusRequester.requestFocus() might not work as expected because Compose focus requires the window to be active.

Test case:

  1. Open two BossTerm windows
  2. Focus on window A
  3. Right-click in window B (unfocused)
  4. Dismiss menu
  5. Verify keyboard input works in window B

Mitigation: The existing window focus logic in ContextMenuController.kt:126-138 should handle this, but worth verifying.

2. PopupMenuListener Cleanup

ContextMenuController.kt:112-120 adds a PopupMenuListener but never removes it. Since currentPopup is set to null on line 117, the popup becomes garbage-collectable with its listener, so this is likely fine. However, if popups are reused or long-lived, consider explicit cleanup.

3. Embedded Example Dependencies

The new embedded-example/build.gradle.kts doesn't declare JVM target consistency with parent project. Verify it inherits the correct JDK 17 settings.


⚡ Performance Considerations

1. LaunchedEffect Efficiency

The LaunchedEffect(contextMenuState.isVisible) pattern is efficient since it only triggers on state changes. The 50ms delay is negligible for user interaction.

2. Menu State Updates

Setting _menuState.value = MenuState() creates a new object on every dismissal. Since MenuState is likely a data class with few fields, this is acceptable.


🔒 Security Considerations

No security concerns identified. The changes are UI-focused and don't introduce:

  • XSS risks (no web content)
  • Command injection (no shell interaction changes)
  • Resource exhaustion (bounded coroutines)

📝 Documentation

Strengths:

  • Clear KDoc for ContextMenuItem
  • Inline comments explain focus restoration logic
  • PR description provides excellent testing guidance

Suggestions:

  • Add a comment explaining the 50ms delay rationale (e.g., "Allow Swing event queue to settle")
  • Document the enableDebugPanel parameter in ProperTerminal.kt KDoc

🧪 Test Coverage

The PR includes comprehensive manual testing via embedded-example, but lacks automated tests. Consider adding:

  1. Unit tests for PopupMenuListener state updates
  2. Integration tests for focus restoration after menu dismissal
  3. Regression tests for issue Terminal input stops working after context menu dismissal when embedded #126 scenario

However, given Compose Desktop's UI testing limitations, manual testing may be acceptable for this change.


✨ Recommendations

Before Merge:

  1. ✅ Test multi-window scenario (see "Potential Issues" feat: Terminal rendering improvements + 4 high-priority features (#2, #3, #4, #5) #1)
  2. ✅ Verify embedded-example builds and runs correctly
  3. ✅ Test all dismissal methods (click item, outside click, Escape)

Future Enhancements:

  1. Extract focus restoration helper to reduce duplication
  2. Consider standardizing nullable callback patterns
  3. Add automated UI tests if Compose Desktop testing improves

📊 Overall Assessment

Rating: ⭐⭐⭐⭐½ (4.5/5)

This is a high-quality PR that:

  • ✅ Solves the reported issue effectively
  • ✅ Follows existing codebase patterns
  • ✅ Includes excellent testing infrastructure
  • ✅ Adds valuable extensibility (custom context menus)
  • ✅ Has no critical bugs or security issues

Minor improvements noted above are suggestions for polish, not blockers.

Recommendation: Approve with optional follow-ups


🤝 Great Work!

The embedded-example module is particularly valuable—it provides a reusable test harness for future embedded terminal issues. This demonstrates thorough problem-solving and consideration for maintainability.

cc @kshivang

When BossTerm is embedded in a parent application, terminal keyboard
input would stop working after dismissing the context menu or clicking
outside the terminal. This was because focus was not being restored to
the terminal's FocusRequester.

Changes:
- Add PopupMenuListener to ContextMenuController to track popup dismissal
- Add LaunchedEffect in ProperTerminal to restore focus when menu closes
- Add focusRequester.requestFocus() to single-click handler
- Make tab/split/settings callbacks nullable to hide non-functional menu items
- Add enableDebugPanel parameter to hide debug panel option for embedded terminals
- Add contextMenuItems API for custom context menu items in EmbeddableTerminal
- Add embedded-example module demonstrating embedded terminal usage

New API for custom context menu items:
```kotlin
EmbeddableTerminal(
    contextMenuItems = listOf(
        ContextMenuItem(
            id = "my_action",
            label = "My Custom Action",
            action = { /* handle action */ }
        )
    )
)
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

PR Review: Fix Terminal Focus After Context Menu Dismissal (#126)

Overall Assessment

Status:LGTM with minor suggestions

This is a well-implemented fix for a legitimate embedded terminal focus issue. The solution follows established patterns in the codebase and includes comprehensive testing infrastructure.


🎯 Core Fix Quality

ContextMenuController.kt

  • Excellent: Added PopupMenuListener to track menu dismissal (lines 121-129)
  • Correct: Updates _menuState.value = MenuState() when popup becomes invisible
  • Clean: Removed redundant focus restoration logic (old lines 151-169) that only handled window-level focus
  • Key insight: The state update triggers the LaunchedEffect in ProperTerminal.kt for proper focus restoration

ProperTerminal.kt

  • Consistent Pattern: Focus restoration follows existing patterns for search bar (lines 1448-1462) and debug panel (lines 1467-1473)
  • Proper Implementation: Uses LaunchedEffect with contextMenuState.isVisible as key (lines 1479-1487)
  • 50ms delay: Appropriate timing to ensure menu is fully dismissed before focus restoration
  • ⚠️ Minor: Consider adding a comment explaining why the delay is necessary

EmbeddableTerminal.kt

  • Feature Addition: Custom context menu items with sections and submenus
  • Clean API: ContextMenuItem, ContextMenuSection, ContextMenuSubmenu sealed interface hierarchy
  • Backward Compatible: contextMenuItems parameter defaults to emptyList()
  • Good Design: enableDebugPanel = false for embedded mode makes sense

🏗️ Architecture & Design

Strengths:

  1. Separation of Concerns: Menu state management in ContextMenuController, focus restoration in ProperTerminal
  2. Reactive Design: Uses Compose state to communicate menu visibility changes
  3. Extensibility: Custom menu items API is well-structured with sealed interface hierarchy
  4. Testing Infrastructure: New embedded-example module provides reproducible test environment

Design Considerations:

  1. ⚠️ MenuElement Hierarchy: The refactored MenuElement sealed class hierarchy (lines 28-58 in ContextMenuController.kt) is good, but note there is now duplication between internal and public API types. This is acceptable for API stability, but consider documenting why both exist.

  2. Callback Nullability: Making callbacks like onNewWindow, onSplitVertical nullable in ProperTerminal.kt (lines 117-123) is correct for embedded scenarios where these features do not apply.


🧪 Testing & Validation

embedded-example Module:

  • Comprehensive: 290-line test app with sidebar, toolbar, status bar
  • Scenario Coverage: Sidebar buttons that steal focus, custom context menu items
  • User Experience: Status bar guides users on how to test (line 284)
  • Demo Value: Shows submenu and section features of custom context menu API

Test Plan Coverage:

  • ✅ Context menu dismissal via click
  • ✅ Context menu dismissal via Escape
  • ✅ Context menu dismissal via menu item selection
  • ✅ Focus transitions between sidebar buttons and terminal

🔒 Security & Performance

Security:

  • ✅ No security concerns identified
  • ✅ Focus restoration does not expose sensitive state
  • ✅ Custom menu items execute user-provided callbacks safely

Performance:

  • ✅ Minimal overhead: Single LaunchedEffect monitoring boolean state
  • ✅ 50ms delay is negligible for user interaction
  • ✅ Menu element conversion (convertToMenuElement) is lightweight

📝 Code Quality

Strengths:

  • ✅ Clear documentation comments on all new APIs
  • ✅ Consistent naming conventions
  • ✅ No unnecessary complexity
  • ✅ Follows Kotlin idioms

Minor Issues:

  1. Line 927 in ProperTerminal.kt: Added focusRequester.requestFocus() after single-click

    • ⚠️ This was not mentioned in the PR description
    • ✅ But it is a sensible addition - ensures focus after click
    • 💡 Suggestion: Mention this in PR description or add a comment explaining it prevents focus loss after mouse-only interaction
  2. Custom Menu Item Prefixing (line 422 in ContextMenuController.kt):

    • Uses custom_ prefix to avoid ID collisions
    • ✅ Good practice, but consider documenting this in the API comment for ContextMenuItem

🐛 Potential Issues

None identified, but some observations:

  1. Focus Restoration Race Condition: The 50ms delay in LaunchedEffect (line 1485) is a timing assumption

    • ✅ Likely sufficient in practice
    • 💡 Consider: If issues arise, could listen to AWT focus events instead of using delay
  2. Multiple Terminals: If multiple embedded terminals exist, menu dismissal state is per-controller

    • ✅ Correct behavior - each terminal has its own contextMenuController

✨ Suggestions for Future Work

  1. API Documentation: Add KDoc example for EmbeddableTerminal.contextMenuItems showing the custom menu API usage
  2. Focus Debug Logging: Consider adding debug logs (behind debugModeEnabled setting) for focus restoration events
  3. embedded-example: Could add a split-pane scenario with multiple terminals to test focus isolation

📋 Checklist


🎬 Recommendation

Approve and Merge - This is a solid fix. The optional improvements below can be addressed in this PR or a follow-up:

Optional improvement:
Add a comment at line 1479 in ProperTerminal.kt explaining that the 50ms delay ensures popup is fully dismissed before focus restoration, and that this handles dismissal via menu item, outside click, or Escape key.

Great work on this fix! The embedded-example module is particularly valuable for regression testing.


Generated with Claude Code

Skip automatic separator when first custom item is already a
ContextMenuSection, which renders its own separator.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review: Fix terminal focus after context menu dismissal (#126)

Summary

This PR successfully addresses issue #126 by implementing proper focus restoration when the context menu is dismissed in embedded scenarios. The solution is well-architected and includes comprehensive testing infrastructure.


✅ Strengths

1. Clean Focus Restoration Pattern

The focus restoration mechanism follows existing patterns in the codebase (search bar, debug panel) and uses a dual approach:

  • AWT level: PopupMenuListener tracks menu dismissal and updates state
  • Compose level: LaunchedEffect watches state and restores focus
// ContextMenuController.kt:121-129
popup.addPopupMenuListener(object : javax.swing.event.PopupMenuListener {
    override fun popupMenuWillBecomeInvisible(e: javax.swing.event.PopupMenuEvent?) {
        _menuState.value = MenuState()
        currentPopup = null
    }
})

// ProperTerminal.kt:1481-1489
LaunchedEffect(contextMenuState.isVisible) {
    if (!contextMenuState.isVisible) {
        kotlinx.coroutines.delay(50)
        focusRequester.requestFocus()
    }
}

The 50ms delay is appropriate for allowing the AWT event queue to settle before Compose focus restoration.

2. Excellent Test Infrastructure

The new embedded-example module is well-designed:

  • Realistic embedded scenario with sidebar and toolbar
  • Custom context menu items demonstrate extensibility
  • Clear test plan in PR description
  • Properly configured Gradle build

3. Enhanced Context Menu System

The refactoring to support sections and submenus is well-implemented:

  • Clean sealed class hierarchy (MenuElement, MenuItem, MenuSeparator, MenuSubmenu)
  • Recursive rendering via addElementsToMenu()
  • Smart separator logic that avoids double-separators when first custom item is a section

4. API Improvements

Making optional callbacks nullable ((() -> Unit)?) instead of empty lambdas is cleaner:

onNewTab: (() -> Unit)? = null,  // Better
// vs
onNewTab: () -> Unit = {},  // Old way

🔍 Issues & Suggestions

1. Potential Double Focus Restoration ⚠️

Location: ProperTerminal.kt:924-927 and ProperTerminal.kt:1481-1489

There's a subtle issue where focus might be restored twice:

// Single click handler (line 924)
isDragging = false
focusRequester.requestFocus()  // Focus restored on mouse release

// Context menu close handler (line 1481)
LaunchedEffect(contextMenuState.isVisible) {
    if (!contextMenuState.isVisible) {
        kotlinx.coroutines.delay(50)
        focusRequester.requestFocus()  // Focus restored when menu closes
    }
}

Scenario: If user clicks a menu item, both handlers may fire:

  1. Menu action executes → menu closes → LaunchedEffect triggers
  2. Mouse release event → click handler triggers

Impact: Likely benign (redundant focus requests), but worth verifying there's no flicker or race condition.

Suggestion: Consider tracking whether the menu was just closed to skip the click handler's focus request, or verify in testing that this doesn't cause issues.

2. Separator Handling Inconsistency ⚠️

Location: ContextMenuController.kt:182-187

The code checks if id.startsWith("separator") for backward compatibility, but this is fragile:

is MenuItem -> {
    if (element.id.startsWith("separator")) {
        // Renders as separator even though it's a MenuItem
        menu.add(JSeparator().apply { ... })
    } else {
        menu.add(JMenuItem(element.label).apply { ... })
    }
}

Issues:

  • Mixes concerns: MenuItem should not also be a separator
  • User could accidentally create MenuItem(id = "separator_foo", ...) and get unexpected rendering
  • The proper type (MenuSeparator) exists now

Suggestion:

  • Remove this backward-compatibility hack and use MenuSeparator exclusively
  • Update all existing separator creation code to use MenuSeparator instead of MenuItem(id = "separator_*", ...)
  • This makes the type system more reliable

3. Missing Focus Restoration on Escape Key ⚠️

Location: ContextMenuController.kt:121-129

The PopupMenuListener only has code in popupMenuWillBecomeInvisible(), but the listener interface has three methods:

override fun popupMenuWillBecomeVisible(e: PopupMenuEvent?) {}
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {
    _menuState.value = MenuState()
    currentPopup = null
}
override fun popupMenuCanceled(e: PopupMenuEvent?) {}  // Empty!

Question: Does pressing Escape trigger popupMenuCanceled() instead of popupMenuWillBecomeInvisible()? If so, the state won't update and focus won't restore.

Testing: Add explicit test case:

  • Right-click → Press Escape → Verify keyboard input works

Fix if needed: Also update state in popupMenuCanceled().

4. Platform-Specific Font Assumption

Location: Multiple files using .AppleSystemUIFont

font = Font(".AppleSystemUIFont", Font.PLAIN, 13)

Issue: This is macOS-specific. On Linux/Windows, this font may not exist and will fall back to a default, potentially with different sizing.

Suggestion: Use platform-aware font selection:

font = if (System.getProperty("os.name").contains("Mac")) {
    Font(".AppleSystemUIFont", Font.PLAIN, 13)
} else {
    Font(Font.SANS_SERIF, Font.PLAIN, 13)
}

Or extract to a utility function for consistency.

5. Hardcoded Dark Theme Colors

Location: ContextMenuController.kt (multiple places)

All menu colors are hardcoded for dark theme:

background = Color(0x2B, 0x2B, 0x2B)
foreground = Color.WHITE

Issue: Doesn't respect system theme preferences or terminal theme settings.

Enhancement: Consider passing theme colors from TerminalSettings or detecting system theme.

6. EmbeddableTerminal API Exposure

Location: EmbeddableTerminal.kt:20-46

The context menu data classes are defined in the public API file but use sealed interfaces (which is good). However, the naming might conflict with the internal ContextMenuController.MenuElement hierarchy.

Observation: Two parallel hierarchies exist:

  • Public API: ContextMenuElementContextMenuItem, ContextMenuSection, ContextMenuSubmenu
  • Internal: ContextMenuController.MenuElementMenuItem, MenuSeparator, MenuSubmenu

Suggestion: Consider namespacing the internal types (e.g., ContextMenuController.InternalMenuItem) to avoid confusion, or unify the hierarchies if possible.


🛡️ Security & Performance

Security ✅

  • No security concerns identified
  • Proper input sanitization for menu items
  • No injection risks in action callbacks

Performance ✅

  • 50ms delay is negligible for user perception
  • LaunchedEffect properly scoped to state changes
  • Menu rendering is efficient (native AWT)

Memory Management ✅

  • Proper cleanup via currentPopup = null
  • No memory leaks from listener registration (JPopupMenu lifecycle handles it)

📋 Testing Recommendations

Required Tests (from PR description)

  • Run embedded-example app
  • Dismiss by clicking menu item → verify input works
  • Dismiss by clicking outside → verify input works
  • ADD: Dismiss by pressing Escape → verify input works (see Issue 🔗 Implement hyperlink detection and click handling #3 above)
  • Click sidebar buttons → verify focus restored

Additional Test Cases

  1. Rapid menu open/close: Open context menu, immediately right-click again → verify no stuck menus
  2. Multiple windows: Open 2 windows, right-click in one while other is focused → verify correct focus behavior
  3. Custom menu items: Verify sections, submenus render correctly and trigger actions
  4. Edge case: Right-click and drag away before releasing → verify menu doesn't show or closes properly

📊 Code Quality Metrics

Metric Rating Notes
Architecture ⭐⭐⭐⭐⭐ Clean separation of concerns
Maintainability ⭐⭐⭐⭐☆ Good, minor improvement opportunities
Documentation ⭐⭐⭐⭐☆ Well-documented, could add more inline comments
Test Coverage ⭐⭐⭐⭐☆ Excellent example module, needs unit tests
Robustness ⭐⭐⭐⭐☆ Solid, but verify edge cases (Escape key, double focus)

🎯 Recommendation

Approve with minor fixes suggested

This is a well-crafted PR that solves a real usability issue. The implementation is clean and follows project conventions. I recommend:

  1. Before merge: Test Escape key dismissal (Issue 🔗 Implement hyperlink detection and click handling #3) and add to test plan if needed
  2. Follow-up PR: Consider refactoring separator handling (Issue 🔍 Implement text search/find functionality (Ctrl+F) #2) for type safety
  3. Future enhancement: Platform-aware font selection and theme support (Issues ⚙️ Implement user settings and configuration system #4, 🌏 Implement IME (Input Method Editor) support for CJK languages #5)

The core focus restoration logic is sound and the test infrastructure is excellent. Great work! 🚀


📝 Minor Nits

  • ProperTerminal.kt:131: Comment formatting is inconsistent (some end with period, some don't)
  • ContextMenuController.kt:236: executeItem() method is unused in this PR - consider removing if obsolete
  • Embedded example could benefit from a README explaining its purpose

Files Reviewed: 6 files, 574 additions, 69 deletions
Review Time: Comprehensive analysis completed
Risk Level: Low - well-isolated changes with good test coverage

- Create docs/embedding.md with complete embedding guide
- Document ContextMenuItem, ContextMenuSection, ContextMenuSubmenu API
- Add examples for custom context menus
- Document session persistence and focus management
- Add Documentation section to README with link to docs

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review: Fix Terminal Focus After Context Menu Dismissal (#126)

Summary

This PR successfully addresses issue #126 by implementing focus restoration after context menu dismissal in embedded terminal scenarios. The implementation is clean, follows existing patterns, and includes comprehensive documentation and testing infrastructure.

✅ Strengths

1. Core Fix is Solid

The focus restoration mechanism in ProperTerminal.kt:1481-1489 follows the established pattern used for search bar and debug panel:

LaunchedEffect(contextMenuState.isVisible) {
  if (!contextMenuState.isVisible) {
    kotlinx.coroutines.delay(50)
    focusRequester.requestFocus()
  }
}

This is exactly the right approach - consistent with existing code and leveraging Compose's reactive state.

2. Proper State Management

The PopupMenuListener in ContextMenuController.kt:121-128 correctly updates the menu state when the popup becomes invisible, ensuring the LaunchedEffect trigger works for all dismissal methods (click item, click outside, Escape key).

3. Enhanced Context Menu System

The refactoring to support MenuElement sealed class with MenuItem, MenuSeparator, and MenuSubmenu is well-designed:

  • Type-safe and extensible
  • Recursive structure for submenus
  • Clean separation of concerns

4. Excellent Documentation

The new docs/embedding.md is comprehensive and well-structured:

  • Clear API reference with parameter tables
  • Session persistence patterns
  • Context menu customization examples
  • Good use of code examples

5. Testing Infrastructure

The embedded-example module is a valuable addition:

🔍 Observations & Minor Suggestions

1. Nullable Callback Pattern Change

ProperTerminal.kt:117-122 changes several callbacks from non-null with empty default to nullable:

onNewTab: (() -> Unit)? = null,
onNewWindow: (() -> Unit)? = null,
onShowSettings: (() -> Unit)? = null,

Question: Is this a breaking change for existing consumers? If these were previously () -> Unit with {} default, changing to nullable requires call-site updates.

Suggestion: Consider mentioning this in the PR description as a potential breaking change, or maintain backwards compatibility with = {} defaults if these are public APIs.

2. Magic Number - 50ms Delay

The 50ms delay in focus restoration appears in 3 places:

  • Line 1476 (debug panel)
  • Line 1486 (context menu) ← new
  • Presumably elsewhere for search bar

Suggestion: Consider extracting to a named constant:

private const val FOCUS_RESTORATION_DELAY_MS = 50L

This improves maintainability and documents the purpose.

3. Additional Click Handler Focus Restoration

Line 928 adds focusRequester.requestFocus() in the single-click handler. While this ensures focus after clicks, verify this doesn't conflict with:

  • Shift+Click for selection (line 1007-1040)
  • Mouse reporting mode forwarding (line 871-888)

Testing: Click around while mouse reporting is active (e.g., in vim) to ensure focus restoration doesn't interfere.

4. Custom Context Menu Item ID Prefix

ContextMenuController.kt:394-402 prefixes custom item IDs with custom_:

id = custom_DOLLAR_element_id

Consideration: What if a user passes id = separator or id = copy? The prefix prevents collisions, but is there validation or documentation about reserved IDs?

Suggestion: Add a note in docs/embedding.md that IDs are automatically prefixed to avoid collisions.

5. Separator Backwards Compatibility

Line 182 checks element.id.startsWith(separator) to maintain backwards compatibility with old-style separator items. This is good defensive coding, but:

Question: Are there existing code paths that create MenuItem with id=separator_* instead of using MenuSeparator? If not, this check could be removed for simplicity.

🛡️ Security & Performance

Security

✅ No security concerns identified:

  • No user input is directly executed
  • Context menu actions are caller-defined closures
  • No XSS/injection vectors in menu rendering

Performance

✅ No performance concerns:

  • State updates are minimal (menu visibility boolean)
  • 50ms delay is acceptable for focus restoration
  • Menu element conversion is negligible overhead

📋 Test Coverage

Manual Testing Checklist (from PR description)

The test plan is comprehensive. Additionally test:

  • Shift+Click selection while context menu was recently dismissed
  • Context menu → Escape → immediately start typing (no 50ms gap perceived?)
  • Multiple rapid context menu open/close cycles
  • Custom context menu items with long-running actions

Automated Tests

Observation: This PR doesn't include unit tests for:

  • convertToMenuElement() function
  • addElementsToMenu() recursive logic
  • Focus restoration timing

Note: Given the UI-heavy nature and the manual testing harness provided, this is acceptable, but consider adding tests in a follow-up if this becomes a source of regressions.

📚 Documentation

Strengths

  • ✅ Comprehensive docs/embedding.md with all API parameters
  • ✅ Code examples for common scenarios
  • ✅ README.md updated with links to embedding guide
  • ✅ Inline KDoc comments for new data classes

Minor Gap

  • The enableDebugPanel parameter (line 134) isn't documented in docs/embedding.md API table
  • Consider adding a note about enableDebugPanel = false for production embedded use

🎯 Architecture & Code Quality

Design Patterns

Excellent use of patterns:

  • Sealed class for menu elements (type-safe union)
  • Reactive state with Compose LaunchedEffect
  • Listener pattern for AWT popup events
  • Builder pattern for menu construction

Code Quality

High quality:

  • Clear naming conventions
  • Consistent formatting
  • Proper use of Kotlin idioms (when, data classes, default parameters)
  • No code smells detected

Consistency

Follows existing conventions:

  • Matches focus restoration pattern for search/debug panels
  • Dark theme styling consistent with existing menus
  • Error handling via null checks matches codebase style

🚀 Recommendations

Before Merge

  1. Verify breaking changes: Confirm that nullable callback changes won't break existing consumers, or document the migration path
  2. Test mouse reporting interaction: Ensure the click focus restoration (line 928) doesn't interfere with vim/tmux mouse handling
  3. Add enableDebugPanel to docs: Update docs/embedding.md API table

Future Enhancements (Optional)

  1. Extract FOCUS_RESTORATION_DELAY_MS constant
  2. Add note about ID prefixing to embedding docs
  3. Consider unit tests for menu conversion logic
  4. Validate/document reserved menu item IDs

✅ Verdict

APPROVED with minor suggestions.

This is a well-implemented fix that addresses the root cause of issue #126. The code is clean, follows existing patterns, and includes excellent documentation and testing infrastructure. The embedded example module is particularly valuable for future development.

The minor suggestions above are non-blocking - they're mostly about documentation completeness and potential future maintainability improvements.

Great work! 🎉


Reviewed with focus on: code quality, security, performance, test coverage, documentation, and adherence to project conventions per CLAUDE.md

- Create tabbed-example module demonstrating TabbedTerminal
- Multiple windows support with menu bar integration
- Window focus tracking for command notifications
- Settings panel overlay example
- Add docs/tabbed-terminal.md with complete guide
- Document MenuActions, keyboard shortcuts, window management
- Add comparison table: EmbeddableTerminal vs TabbedTerminal
- Update README Documentation section

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review: PR #127 - Fix Terminal Focus After Context Menu Dismissal

Overview

This PR successfully addresses issue #126 by restoring terminal focus after context menu dismissal in embedded scenarios. The implementation also adds substantial value through custom context menu support and comprehensive documentation.


✅ Strengths

1. Core Fix is Sound

The focus restoration logic in ProperTerminal.kt:1481-1489 follows the established pattern used for search bar and debug panel:

LaunchedEffect(contextMenuState.isVisible) {
  if (!contextMenuState.isVisible) {
    kotlinx.coroutines.delay(50)
    focusRequester.requestFocus()
  }
}

This correctly handles all dismissal scenarios (click item, click outside, Escape key).

2. PopupMenuListener Integration

The PopupMenuListener in ContextMenuController.kt:121-129 properly tracks popup state changes:

  • Updates _menuState.value when menu becomes invisible
  • Cleans up currentPopup reference
  • Enables reactive focus restoration in ProperTerminal

3. Custom Context Menu API

Well-designed public API with three element types:

  • ContextMenuItem: Clean action-based design
  • ContextMenuSection: Flexible separator with optional labels
  • ContextMenuSubmenu: Recursive structure for nested menus

The internal MenuElement sealed class hierarchy mirrors the public API cleanly.

4. Comprehensive Documentation

  • docs/embedding.md (288 lines): Thorough embedding guide with examples
  • docs/tabbed-terminal.md (281 lines): Complete tabbed terminal documentation
  • embedded-example module: Practical test harness demonstrating the fix

5. API Design Consistency

Making tab/window/split callbacks nullable ((() -> Unit)?) is correct for embedded contexts where these features may not be applicable.


🔍 Issues & Concerns

1. Focus Restoration Race Condition (Minor)

Location: ProperTerminal.kt:927 and 1486

Both single-click handler and context menu close handler call focusRequester.requestFocus():

// Line 927: After mouse release
focusRequester.requestFocus()

// Line 1486: After menu closes (50ms delay)
kotlinx.coroutines.delay(50)
focusRequester.requestFocus()

Risk: If user clicks a menu item, both handlers fire. The delayed call (1486) may override any intervening focus changes.

Recommendation: Consider checking if context menu is visible before calling requestFocus in the click handler, or track last focus request timestamp to avoid redundant calls.

2. Missing Error Handling in Context Menu Building

Location: ContextMenuController.kt:177-233

The addElementsToMenu() method recursively builds menu structures but doesn't validate:

  • Circular submenu references (could cause stack overflow)
  • Empty submenu lists
  • Duplicate IDs across elements

Recommendation:

private fun addElementsToMenu(menu: javax.swing.JComponent, elements: List<MenuElement>) {
    elements.forEach { element ->
        when (element) {
            is MenuSubmenu -> {
                require(element.items.isNotEmpty()) { 
                    "Submenu cannot be empty" 
                }
                // ... existing code
            }
            // ... other cases
        }
    }
}

3. Potential Memory Leak in PopupMenuListener

Location: ContextMenuController.kt:121

The listener captures state in its closure but is added to every popup. If currentPopup reference is lost without disposal, listeners accumulate.

Current mitigation: Line 107-109 explicitly dismisses old popups, which likely prevents this.

Recommendation: Consider explicit listener removal or WeakReference pattern if popup lifecycle becomes more complex.

4. ID Prefix Collision Risk

Location: ContextMenuController.kt:399-418

The convertToMenuElement() function prefixes custom IDs with "custom_", "custom_section_", or "custom_submenu_". If a user provides ID "submenu_foo", the internal ID becomes "custom_submenu_foo", which could collide with other IDs.

Recommendation: Use more distinct separators (e.g., "custom__item__ID") or UUID-based internal IDs.

5. Disabled Debug Panel in Embedded Mode

Location: EmbeddableTerminal.kt:204

enableDebugPanel = false,  // Hide debug panel in embedded mode

Question: Is there a use case for embedding applications wanting debug capabilities? Consider making this configurable via parameter rather than hardcoded.

6. Font Loading Not Documented for Embedded Usage

The embedding guide doesn't mention custom font requirements. Based on CLAUDE.md, BossTerm requires bundled fonts for proper rendering.

Recommendation: Add a "Resources" section to docs/embedding.md documenting bundled font requirements and how to override them.


🔒 Security Assessment

No security concerns identified:

  • Context menu actions are caller-provided closures (no injection risk)
  • No dynamic code execution or reflection
  • AWT JPopupMenu is standard Swing component
  • Focus management doesn't expose sensitive state

🧪 Test Coverage

✅ Manual Testing

The embedded-example module provides good manual test coverage:

  • Sidebar buttons that steal focus
  • Context menu dismissal scenarios
  • Custom menu items with sections/submenus

❌ Automated Testing

Missing:

  • Unit tests for ContextMenuController element building
  • Integration tests for focus restoration behavior
  • Tests for edge cases (empty submenus, circular refs, etc.)

Recommendation: Add unit tests for convertToMenuElement() and addElementsToMenu() to catch structural issues.


🚀 Performance Considerations

✅ Good:

  • LaunchedEffect properly scoped to state changes (no excessive recomposition)
  • Menu state is mutableStateOf (reactive updates only when changed)
  • 50ms delay is reasonable (balances focus restoration with event processing)

⚠️ Minor Concern:

The createTerminalContextMenuItems() function rebuilds the entire menu list on every invocation. If the parent composable recomposes frequently, this could allocate unnecessarily.

Recommendation: Consider using remember to cache menu items if all parameters are stable.


📋 Suggested Improvements

Priority 1 (Pre-Merge):

  1. Add validation for empty submenus in addElementsToMenu()
  2. Document font requirements in embedding guide
  3. Add parameter for enableDebugPanel in EmbeddableTerminal instead of hardcoding

Priority 2 (Follow-up):

  1. Add unit tests for context menu element conversion and building
  2. Consider caching for menu item lists to reduce allocations
  3. Add circular reference detection for submenus (or document limitation)

Priority 3 (Nice to Have):

  1. Focus restoration metrics: Log focus request timing to detect race conditions
  2. Custom menu examples in embedded-example demonstrating all element types
  3. Accessibility testing: Verify keyboard navigation in custom submenus

🎯 Verdict

Recommendation: ✅ APPROVE with minor suggestions

This PR successfully fixes the reported issue (#126) and adds significant value through the custom context menu API and documentation. The core focus restoration logic is sound and follows established patterns.

The identified issues are mostly edge cases and quality-of-life improvements that don't block merging. The Priority 1 suggestions above would strengthen the PR, but the current implementation is production-ready for the primary use case.

Excellent work on the comprehensive documentation and test harness! The embedded-example module will be invaluable for future embedded-context debugging.


📝 Commit Message Quality

✅ Follows repository convention (no Co-Authored-By line)
✅ Includes Claude Code attribution
✅ Clear fix reference (Fixes #126)
✅ Descriptive PR title and body


Reviewed by Claude Sonnet 4.5 via Claude Code
Following BossTerm coding standards from CLAUDE.md

Use SettingsManager to get defaultBackgroundColorWithOpacity
instead of hardcoded Color.Black.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review - PR #127

Excellent work on fixing the focus restoration issue and expanding the embedding API! This is a well-structured PR with good documentation and testing infrastructure.


Strengths

1. Root Cause Analysis - Clear identification of the problem: Focus not restored after JPopupMenu dismissal. Well-documented in PR description and code comments.

2. Consistent Pattern - Follows existing focus restoration pattern used for search bar and debug panel. Uses PopupMenuListener.popupMenuWillBecomeInvisible to track dismissal by any means (click, outside click, Escape). The 50ms delay matches existing patterns.

3. Excellent Testing Infrastructure - New embedded-example module provides comprehensive test scenarios with sidebar and toolbar elements to test focus stealing, plus custom context menu demonstration.

4. API Enhancement - Custom context menu API (ContextMenuItem, ContextMenuSection, ContextMenuSubmenu) is well-designed with recursive structure allowing unlimited nesting and clean separation between public API types and internal implementation.

5. Documentation - Comprehensive docs/embedding.md guide (288 lines) with API reference, parameter descriptions, usage examples, and session persistence patterns.


Code Quality Observations

Minor Issues

Single-Click Focus Restore (ProperTerminal.kt:927) - This was added to restore focus on mouse click, but may conflict with context menu focus restoration. If user clicks to open context menu, this requests focus immediately, then the context menu listener requests it again after dismissal. Consider if this is needed, or if it should be conditional.

State Update Timing (ContextMenuController.kt:123-125) - State is updated synchronously in Swing thread. Potential race condition if showMenu() is called concurrently from a coroutine. Consider using SwingUtilities.invokeLater for state updates or document threading expectations.

Nullable Callbacks (ProperTerminal.kt:122-142) - The PR changes several callbacks to nullable (onNewTab, onNewWindow, onShowSettings, onSplitHorizontal, onSplitVertical). This is a BREAKING API CHANGE. The default behavior changes from no-op {} to null. While addTabManagementActions() uses safe fallback, this changes ProperTerminal signature in a way that might surprise embedders. Consider documenting as breaking change in release notes.


Potential Bugs (None Critical)

Focus Timing - The 50ms delay is arbitrary. On slower systems or under load, this might not be enough. Consider SwingUtilities.invokeLater instead for guaranteed execution after Swing event queue clears.

PopupMenuListener Thread Safety - The _menuState.value assignment happens on Swing EDT, but mutableStateOf is typically used from Compose main thread. While Compose State is thread-safe for reads, consider if explicit thread synchronization is needed.

currentPopup Cleanup - If showMenu() is called rapidly, the currentPopup might not be fully dismissed before new one is shown. Brief window where two popups could coexist (though code correctly dismisses before showing new one at line 107).


Performance Considerations

Positive: Focus restoration is event-driven (no polling), state updates are minimal (only when menu opens/closes), custom menu items passed as immutable lists (no unnecessary recomposition).

Neutral: convertToMenuElement() creates new objects for each custom item, but acceptable since context menus are infrequent. Recursive submenu creation could be expensive for deeply nested menus, but unlikely in practice.


Security Concerns

None identified. No command injection, XSS, or OWASP top 10 vulnerabilities introduced. Custom menu actions are user-provided callbacks, so security is caller responsibility.


Test Coverage

Excellent manual test plan provided (context menu dismissal by click/outside/Escape, focus stealing). Suggested additions: (1) Unit test for ContextMenuController state transitions, (2) Test rapid context menu opening/closing, (3) Test with enableDebugPanel = false, (4) Test custom context menu with nested submenus (3+ levels deep).


Recommendations

High Priority: (1) Document the breaking API change (nullable callbacks in ProperTerminal), (2) Review PopupMenuListener state update thread safety.

Medium Priority: (3) Consider replacing delay(50) with SwingUtilities.invokeLater, (4) Add unit tests for ContextMenuController.

Low Priority: (5) Test with 100+ custom menu items for performance, (6) Add focus management best practices section to embedding.md.


Final Verdict

APPROVE with minor suggestions. This PR solves the stated problem, follows project conventions, provides excellent testing infrastructure, enhances the embedding API, and includes comprehensive documentation. The identified issues are minor and do not block merging. Consider addressing thread safety and breaking API change documentation before merge.

Great work!


Reviewed by: Claude Sonnet 4.5 | Date: 2025-12-20

@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review - PR #127: Terminal Focus Restoration Fix

Summary

This PR successfully addresses issue #126 by adding focus restoration after context menu dismissal in embedded scenarios. The implementation follows established patterns and includes comprehensive documentation and testing infrastructure.


Strengths

1. Correct Problem Diagnosis

The root cause analysis is accurate - the context menu dismissal didn't restore focus to the terminal's FocusRequester, which is critical in embedded scenarios where focus may return to the parent container.

2. Consistent Pattern Implementation

The fix at ProperTerminal.kt:1481-1489 follows the existing pattern used for search bar and debug panel focus restoration:

  • Uses LaunchedEffect to monitor state changes
  • 50ms delay before requesting focus (matches other handlers)
  • Clean, readable code

3. Proper State Tracking

The PopupMenuListener addition in ContextMenuController.kt:121-129 correctly updates _menuState when the popup becomes invisible, enabling the Compose-based focus restoration to work properly.

4. Excellent Testing Infrastructure

The new embedded-example module is well-designed:

  • Sidebar with focusable buttons to simulate focus stealing
  • Custom context menu items demonstrating the API
  • Clear test scenarios in the PR description
  • Self-contained and easy to run

5. Enhanced API with Custom Context Menus

The addition of contextMenuItems parameter is a valuable feature:

  • Clean API with sealed interfaces (ContextMenuItem, ContextMenuSection, ContextMenuSubmenu)
  • Supports nested submenus and sections with labels
  • Proper conversion layer between public API and internal implementation
  • Comprehensive documentation in docs/embedding.md

🔍 Observations & Suggestions

1. Redundant Window Focus Code Removed

The PR removes the old PopupMenuListener that restored window focus (lines 149-170 in the diff). This is correct because:

  • Window-level focus is handled automatically by AWT
  • Terminal-level focus is now handled by the new LaunchedEffect in ProperTerminal.kt
  • Eliminates redundant invokeLater calls

2. Additional Click Handler Enhancement (ProperTerminal.kt:927)

The PR adds focusRequester.requestFocus() after mouse release. This is a good defensive addition that ensures focus returns even for regular clicks, not just context menu scenarios.

3. Nullable Callbacks Convention

Changed several callbacks to nullable (onNewWindow, onShowSettings, etc.) with default null values. This is appropriate for embedded scenarios where not all features are needed. The implementation correctly handles nulls with the elvis operator (?: {}).

4. Debug Panel Toggle

Added enableDebugPanel: Boolean = true parameter. Good choice to allow disabling debug features in embedded/production scenarios.


⚠️ Potential Issues

1. Minor: Incomplete Documentation (Low Priority)

docs/embedding.md line 210 appears truncated in the diff:

### Menu Structure

Custom items appear below the built-in items (Copy, Paste, Clear, Select All). The men

Likely just a diff truncation artifact, but worth verifying the file is complete.

2. PopupMenuListener.popupMenuCanceled (Very Minor)

In ContextMenuController.kt:128, the popupMenuCanceled method is empty. Consider if this should also reset state like popupMenuWillBecomeInvisible does. However, popupMenuWillBecomeInvisible is called in all dismissal cases, so this may be intentional and correct.

3. Race Condition Potential (Low Risk)

The 50ms delay in LaunchedEffect blocks could theoretically race with user actions. However, this matches existing code and 50ms is negligible for user interaction. Consider if yield() or immediate focus request works, but the current approach is proven.


🧪 Testing Recommendations

The PR includes a manual test plan. Consider adding:

  1. Edge Case: Rapid Menu Open/Close

    • Open context menu, immediately dismiss, open again
    • Verify state doesn't get stuck
  2. Edge Case: Menu Opens While Another Input is Focused

    • Focus sidebar button
    • Right-click terminal (without clicking first)
    • Dismiss menu
    • Verify focus returns to terminal
  3. Nested Submenu Testing

    • Test custom submenu navigation
    • Verify submenus dismiss properly
    • Verify focus restoration after submenu item click
  4. Test on Multiple Platforms

    • macOS (primary target based on CLAUDE.md)
    • Linux (verify AWT behavior consistency)
    • Windows (if supported)

🔒 Security & Performance

No security concerns identified

  • No user input validation issues
  • No command injection vectors
  • Context menu items are controlled by the embedding application

Performance is excellent

  • State updates are minimal
  • No unnecessary recomposition
  • Coroutine overhead is negligible (50ms delay only on menu dismissal)

📋 Code Quality

Follows project conventions (per CLAUDE.md)

  • Consistent with existing focus restoration patterns
  • Proper Kotlin idioms
  • Clear comments explaining intent
  • No backwards-compatibility hacks

Documentation is comprehensive

  • New docs/embedding.md with API reference
  • Code examples for common use cases
  • Clear parameter documentation
  • README updated with links

📊 Test Coverage

⚠️ No automated tests - This is acceptable for UI focus behavior (notoriously difficult to test), but consider:

  • UI integration tests using Compose testing framework
  • Focus behavior assertions if tooling supports it
  • Regression test cases in CI for common scenarios

The manual test infrastructure (embedded-example) is well-designed and sufficient for verification.


Final Verdict

APPROVE - This is a high-quality PR that:

  1. ✅ Correctly fixes the reported issue
  2. ✅ Follows established patterns
  3. ✅ Adds valuable API enhancements
  4. ✅ Includes comprehensive documentation
  5. ✅ Provides excellent testing infrastructure

The implementation is clean, maintainable, and extends BossTerm's embedding capabilities in a thoughtful way.


🎯 Recommended Actions Before Merge

  1. ✅ Verify docs/embedding.md is not truncated (line 210)
  2. ✅ Run the test plan in the PR description
  3. ✅ Test on target platforms (at minimum: macOS)
  4. ✅ Consider the edge case tests mentioned above
  5. ⚠️ Optional: Add a changelog entry or release notes

Great work! 🚀

Use Surface with defaultBackgroundColor instead of Box with
background modifier to properly fill the window background.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review - PR #127: Fix Terminal Focus After Context Menu Dismissal

Summary

This PR successfully addresses issue #126 by implementing focus restoration when the context menu is dismissed in embedded scenarios. The implementation is well-designed and includes comprehensive documentation and testing infrastructure.


Strengths

1. Root Cause Analysis & Solution

  • Excellent diagnosis: The PR correctly identifies that embedded applications don't automatically restore focus to the terminal after context menu dismissal
  • Consistent pattern: The fix follows the existing focus restoration pattern used for search bar and debug panel (lines 1467-1473, 1448-1462 in ProperTerminal.kt)
  • Proper implementation: Uses LaunchedEffect with contextMenuState.isVisible observation and 50ms delay to ensure reliable focus restoration

2. Enhanced Context Menu System

The context menu refactoring adds significant value:

  • Sealed class hierarchy: Clean architecture with MenuElement, MenuItem, MenuSeparator, MenuSubmenu
  • Public API: New ContextMenuItem, ContextMenuSection, ContextMenuSubmenu classes allow embedding applications to customize menus
  • Recursive rendering: addElementsToMenu() properly handles nested submenus
  • Dark theme styling: Consistent visual design maintained

3. Excellent Testing Infrastructure

The new embedded-example module is exemplary:

  • Real-world scenario: Simulates actual embedding use case with sidebar, toolbar, status bar
  • Focus testing: Multiple UI elements (buttons) can steal focus to reproduce issue
  • Complete example: Demonstrates custom context menu with sections and submenus (lines 90-126)
  • Self-documenting: Clear instructions and visual feedback for testing

4. Documentation

  • Comprehensive guides: New docs/embedding.md and docs/tabbed-terminal.md provide clear API references and examples
  • Updated README: Properly links to new documentation
  • API documentation: Clear JavaDoc/KDoc comments on all public APIs

🔍 Code Quality Observations

Minor Issues

1. PopupMenuListener Removal (ContextMenuController.kt, lines 118-146)

The PR removes the window focus restoration logic from PopupMenuListener:

// REMOVED (lines 128-149):
windowToFocus?.toFront()
windowToFocus?.requestFocus()

Analysis: This removal is intentional and correct. The new approach (updating _menuState.value in line 125) triggers the Compose-based focus restoration in ProperTerminal.kt. However:

  • Minor concern: The comment on line 121 says "// Update state when popup is dismissed (by any means)" but doesn't explain why this replaces the old window focus logic
  • Recommendation: Add a comment explaining that state update triggers Compose-side focus restoration

2. Focus Restoration Timing (ProperTerminal.kt, line 1486)

kotlinx.coroutines.delay(50)

Question: Why 50ms? This matches the search/debug panel delays, but:

  • No comment explains the magic number
  • Could this be insufficient on slower systems?

Recommendation: Add a comment explaining the delay purpose (e.g., "Wait for popup to fully dismiss before restoring focus") or extract to a named constant like FOCUS_RESTORATION_DELAY_MS = 50L

3. Nullable Callbacks Pattern (ProperTerminal.kt, lines 123-143)

The PR changes several callbacks from () -> Unit = {} to (() -> Unit)? = null:

onNewWindow: (() -> Unit)? = null,  // Line 123
onShowSettings: (() -> Unit)? = null,  // Line 124
// etc.

Then uses null-coalescing:

onNewTab = onNewTab ?: {},  // Line 431

Analysis: This is cleaner than empty lambdas but adds null-checking overhead. The pattern is consistent with onMoveToNewTab (line 131), so it's architecturally sound. However:

  • Minor inconsistency: Some callbacks remain non-nullable (onCloseTab, onNextTab, etc.)
  • Question: Why make only some callbacks nullable? This creates two patterns in the same function signature

Recommendation: Either make all optional callbacks nullable (preferred for consistency) or document why certain callbacks must always be provided

4. Focus Request After Click (ProperTerminal.kt, line 927)

// Ensure focus is on terminal canvas after click
focusRequester.requestFocus()

Good addition: This ensures single clicks also restore focus, not just context menu dismissals. However:

  • Question: Does this conflict with the context menu focus restoration? If user clicks to open the menu, does this request focus prematurely?
  • Testing needed: Verify this doesn't cause focus flicker when opening the menu

🔒 Security Considerations

✅ No security concerns identified:

  • Focus management is internal to the application
  • No user input validation required
  • No external APIs or network calls
  • Custom context menu actions are provided by the embedding application (trusted code)

Performance Considerations

Focus Restoration Listeners

Current implementation:

val contextMenuState by contextMenuController.menuState
LaunchedEffect(contextMenuState.isVisible) {
    if (!contextMenuState.isVisible) {
        delay(50)
        focusRequester.requestFocus()
    }
}

Analysis:

  • Efficient: Only triggers when visibility changes
  • Lightweight: 50ms delay doesn't block UI thread (uses coroutine)
  • Minimal allocations: State observation reuses existing State<MenuState> object

Context Menu Rendering

New recursive rendering (ContextMenuController.kt, lines 178-233):

Potential concern: Deeply nested submenus could cause stack overflow

Analysis:

private fun addElementsToMenu(menu: javax.swing.JComponent, elements: List<MenuElement>) {
    elements.forEach { element ->
        when (element) {
            is MenuSubmenu -> {
                val submenu = JMenu(element.label).apply { /* ... */ }
                addElementsToMenu(submenu, element.items)  // Recursive call
                menu.add(submenu)
            }
            // ...
        }
    }
}

Risk: If a custom menu has 100+ levels of nesting, stack overflow is possible

Likelihood: Low - context menus rarely exceed 2-3 levels

Recommendation: Add a depth limit check (e.g., max 10 levels) or document maximum nesting depth in API docs


🧪 Test Coverage

Missing Unit Tests

The PR adds significant new functionality but no automated tests:

  1. Focus restoration logic - No test verifying focusRequester.requestFocus() is called when menu closes
  2. Context menu rendering - No test for recursive submenu rendering
  3. Custom menu elements - No test for convertToMenuElement() conversion logic

Impact: Medium - Manual testing via embedded-example is comprehensive, but regressions could be introduced later

Recommendation: Add tests for:

// compose-ui/src/commonTest/kotlin/ai/rever/bossterm/compose/features/ContextMenuControllerTest.kt
@Test
fun `menu state updates to invisible when popup dismissed`()

@Test
fun `nested submenus render correctly`()

@Test  
fun `custom menu elements convert to internal format`()

Manual Testing (Per PR description)

The test plan is thorough:

  • Right-click → dismiss by menu item click → verify keyboard input
  • Right-click → dismiss by outside click → verify keyboard input
  • Right-click → dismiss by Escape → verify keyboard input
  • Click sidebar buttons → click terminal → verify focus restored

📋 Additional Observations

1. Embedded Mode Flag (ProperTerminal.kt, line 135)

enableDebugPanel: Boolean = true,  // Hide debug panel in embedded mode

Good practice: EmbeddableTerminal.kt sets this to false (line 204), preventing debug panel from appearing in embedded contexts

2. API Consistency (EmbeddableTerminal.kt, line 125)

contextMenuItems: List<ContextMenuElement> = emptyList(),

Excellent design: The public API uses ContextMenuElement (simple sealed interface), while internal implementation uses ContextMenuController.MenuElement. The convertToMenuElement() function bridges the gap cleanly.

3. Documentation Quality

The new docs/embedding.md (288 lines) is exceptional:

  • API reference tables with parameter descriptions
  • Multiple code examples (basic, custom menus, session persistence)
  • Testing instructions
  • Clear explanation of focus management

🎯 Recommendations Summary

Required Before Merge:

None - the implementation is production-ready

Recommended Improvements (Future PRs):

  1. Add unit tests for focus restoration and context menu rendering (priority: medium)
  2. Extract magic number: FOCUS_RESTORATION_DELAY_MS = 50L constant (priority: low)
  3. Document nesting depth limit for custom submenus (priority: low)
  4. Consistent callback nullability: Make all optional callbacks nullable or none (priority: low)

Optional Enhancements:

  1. Add depth limit check for recursive submenu rendering
  2. Add integration test using embedded-example as test fixture

Final Verdict

APPROVED - This PR successfully fixes issue #126 with a clean, well-documented solution.

Quality Score: 9/10

  • ✅ Fixes the reported bug completely
  • ✅ Follows existing code patterns
  • ✅ Adds valuable extensibility (custom context menus)
  • ✅ Includes comprehensive documentation
  • ✅ Provides excellent testing infrastructure
  • ⚠️ Missing unit tests (minor concern given thorough manual testing)

Recommendation: Merge after user completes manual testing as outlined in the test plan. The minor improvements suggested above can be addressed in future PRs if desired.

Great work on both the fix and the enhanced embedding API! 🚀

- Create bossterm-app module with Main.kt application entry point
- Move WindowManager and TerminalWindow to compose-ui/window package
- Remove demo folder from compose-ui (now a library-only module)
- Update compose-ui build.gradle.kts to remove application config
- Update TabbedTerminal to import WindowManager from new location
- Update CLAUDE.md and README.md with new build commands
- Update project structure documentation

Run command changes:
- Old: ./gradlew :compose-ui:run
- New: ./gradlew :bossterm-app:run

Module structure:
- bossterm-core-mpp: Core terminal emulation library
- compose-ui: Embeddable terminal UI library
- bossterm-app: Main BossTerm application
- embedded-example: Single terminal embedding example
- tabbed-example: Tabbed terminal embedding example

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review: PR #127 - Fix terminal focus after context menu dismissal

Summary

This PR successfully addresses issue #126 by implementing focus restoration after context menu dismissal in embedded scenarios. The solution is well-designed and includes a comprehensive test module to verify the fix.


Strengths

1. Root Cause Analysis & Solution

The fix correctly identifies and solves the focus issue:

  • Lines 1482-1488 in ProperTerminal.kt: Added LaunchedEffect that monitors contextMenuState.isVisible and restores focus when the menu closes
  • Lines 121-129 in ContextMenuController.kt: PopupMenuListener properly updates state on dismissal, enabling the focus restoration trigger
  • Pattern matches existing focus restoration for search bar and debug panel (lines 1448-1479)

2. Comprehensive Testing Infrastructure

The embedded-example module is excellent:

  • Real-world scenario with sidebar, toolbar, and status bar
  • Buttons that can steal focus to test edge cases
  • Custom context menu items demonstrating the API
  • Clear instructions for testing

3. Code Organization

The refactoring improves project structure:

  • Separated compose-ui (library) from bossterm-app (main application)
  • New example modules (embedded-example, tabbed-example) for demonstration
  • Updated documentation (CLAUDE.md, README.md, new docs/)

4. Robust Menu Dismissal Handling

ContextMenuController.kt lines 121-129 handle all dismissal scenarios:

  • Menu item clicked
  • Clicked outside menu
  • Escape key pressed
  • Multiple popups (prevents stuck menus with currentPopup tracking)

🔍 Issues & Concerns

1. ⚠️ Race Condition in Focus Restoration (Minor)

Location: ProperTerminal.kt:1486

if (\!contextMenuState.isVisible) {
    kotlinx.coroutines.delay(50)  // Why 50ms? Could be brittle
    focusRequester.requestFocus()
}

Concern:

  • The 50ms delay is hardcoded without explanation
  • On slower systems, the menu might not be fully dismissed yet
  • On faster systems, 50ms is unnecessarily long

Recommendation:
Consider documenting WHY the delay is needed (e.g., waiting for AWT popup to fully close), or investigate if there's a more deterministic way to wait for menu dismissal (e.g., checking if the popup is actually closed).

Suggested improvement:

if (\!contextMenuState.isVisible) {
    // Delay needed for AWT JPopupMenu to fully close before focus restoration
    kotlinx.coroutines.delay(50)
    focusRequester.requestFocus()
}

2. ⚠️ Potential Memory Leak (Minor)

Location: ContextMenuController.kt:107-109

currentPopup?.let {
    it.isVisible = false
}

Concern:
The old popup reference isn't explicitly cleared before creating a new one. While setting isVisible = false should trigger the listener that sets currentPopup = null, there's a brief window where both old and new popups exist.

Recommendation:

currentPopup?.let {
    it.isVisible = false
    currentPopup = null  // Explicit cleanup
}

However, line 126 does clean it up in the listener, so this is very minor.

3. 📝 Documentation Gap (Minor)

Location: ProperTerminal.kt:1481-1488

Issue:
The comment says "Restore focus to terminal when context menu closes" but doesn't explain WHY this is needed (i.e., the embedded scenario where parent can steal focus).

Recommendation:

// Restore focus to terminal when context menu closes
// This is critical for embedded scenarios where focus may return to parent container
// instead of the terminal. Fixes #126.
val contextMenuState by contextMenuController.menuState
LaunchedEffect(contextMenuState.isVisible) { ... }

4. 🤔 Consider Edge Case: Rapid Menu Open/Close (Minor)

Scenario: User rapidly opens and closes context menu multiple times

Potential Issue: Multiple LaunchedEffect coroutines may be running simultaneously, each trying to restore focus after 50ms delay.

Impact: Low - last one wins, but could cause unnecessary work

Recommendation: Not critical, but could add cancellation logic if this becomes a problem in practice.


🎯 Best Practices Compliance

Follows CLAUDE.md Guidelines

  • Uses existing patterns (search/debug panel focus restoration)
  • No backwards-compatibility hacks
  • Clear, technical comments
  • Proper git workflow (dev branch → master)

Security

No security concerns detected

Performance

  • Minimal overhead: single state observer + 50ms delay on menu close
  • No performance regressions expected

Test Coverage

While no automated tests, the embedded-example module provides:

  • Manual test scenarios documented in PR description
  • Clear reproduction steps
  • Real-world use case demonstration

📊 Overall Assessment

Category Rating Notes
Code Quality ⭐⭐⭐⭐ (4/5) Clean, follows patterns, minor improvements possible
Bug Fix Effectiveness ⭐⭐⭐⭐⭐ (5/5) Correctly solves the reported issue
Documentation ⭐⭐⭐⭐ (4/5) Good, but could explain timing/rationale better
Testing ⭐⭐⭐⭐ (4/5) Excellent manual test module, no automated tests
Architecture ⭐⭐⭐⭐⭐ (5/5) Proper separation, reusable patterns

Recommendation: APPROVE with Minor Suggestions

This PR successfully fixes issue #126 with a well-designed solution. The focus restoration pattern matches existing code, and the comprehensive test module demonstrates the fix works correctly.

Action Items (Optional Improvements):

  1. ✏️ Add comment explaining why 50ms delay is needed
  2. 📝 Reference issue Terminal input stops working after context menu dismissal when embedded #126 in the focus restoration comment
  3. 🧪 Consider adding automated test if feasible (low priority)

The code is ready to merge. The minor suggestions above are optional improvements that could be addressed in a follow-up if desired.


Great work on the detailed problem analysis, clean implementation, and comprehensive test infrastructure! 🚀

- Add explanatory comment for context menu focus delay (#126)
- Clarify why 50ms delay is needed for AWT JPopupMenu dismissal
- Add similar comment for debug panel focus restoration

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@kshivang kshivang merged commit c1641da into master Dec 20, 2025
7 checks passed
@claude
Copy link
Copy Markdown

claude Bot commented Dec 20, 2025

Code Review - PR #127: Fix Terminal Focus After Context Menu Dismissal

This PR successfully addresses issue #126 by implementing focus restoration when the context menu closes. The solution is well-architected and follows existing patterns in the codebase.

✅ Strengths

1. Root Cause Analysis - Correctly identifies that in embedded scenarios, focus doesn't automatically return to the terminal's FocusRequester after AWT JPopupMenu dismissal. The PopupMenuListener implementation is the right approach.

2. Consistent Focus Restoration Pattern - The new context menu focus restoration follows the same pattern as existing debug panel and search bar handlers (LaunchedEffect monitoring state + 50ms delay + focusRequester.requestFocus).

3. Comprehensive Testing Module - The embedded-example module is well-designed with sidebar buttons that steal focus, custom context menu items, and clear test instructions.

4. API Design Improvements - Converting callbacks to nullable types makes the API more explicit about optional features for standalone vs embedded usage.

5. PopupMenuListener Implementation - Correctly captures all dismissal scenarios: clicking menu item, clicking outside, pressing Escape, and programmatic dismissal.

🔍 Issues & Suggestions

1. Race Condition Risk (Minor) - File: ContextMenuController.kt:121-129
If showNativeMenuAtScreen is called again before the listener fires, the new popup's dismissal could incorrectly clear state. Suggestion: Track popup identity to ensure state updates match the correct instance.

2. Missing Null Check - File: ProperTerminal.kt:925
focusRequester.requestFocus() is called unconditionally on mouse release. In complex UIs with multiple terminals, this could steal focus unexpectedly. Suggestion: Add guard condition like if (isActiveTab).

3. Hard-Coded Delay Values - File: ProperTerminal.kt:1476, 1487
The 50ms delay is hard-coded in multiple places. Suggestion: Extract to a constant with documentation explaining platform-specific tuning.

4. Context Menu State Observation - File: ProperTerminal.kt:1484
If menuState updates frequently, the LaunchedEffect could trigger repeatedly. Suggestion: Use snapshotFlow with distinctUntilChanged.

5. Documentation Gap - The customContextMenuItems parameter lacks KDoc explaining supported elements.

6. Missing Test Coverage - No automated UI tests for focus restoration lifecycle. Empty test plan checklist in PR description.

🛡️ Security & Performance

Security: No issues found. Context menu actions are caller-provided lambdas, focus restoration uses type-safe Compose API.

Performance: Negligible impact. Context menus are infrequent (<1 Hz). The 50ms delay + focus traversal is acceptable overhead.

📚 Code Quality

✅ Follows CLAUDE.md guidelines (no backwards-compatibility hacks, clear comments)
✅ Consistent with codebase style (idiomatic Compose, Kotlin conventions)
⚠️ Minor issue: if (element.id.startsWith("separator")) in ContextMenuController.kt:182 is fragile. Should use sealed classes properly with when expressions.

📦 Module Restructuring

✅ Excellent separation: Library code (compose-ui), Application code (bossterm-app), Examples (embedded-example, tabbed-example)
⚠️ Build configuration duplication: bossterm-app/build.gradle.kts duplicates ~100 lines. Consider extracting to buildSrc.

🎯 Final Verdict: Approve with Minor Suggestions ✅

This is a well-executed fix that solves the reported issue, follows architectural patterns, and improves the embeddable API.

Must Fix: None (PR is functional as-is)

Should Consider: Test plan documentation, extract delay constant, add isActiveTab guard in mouse handler

Nice to Have: Eliminate ID-based separator detection, add automated UI tests, extract shared build config

Great work on this PR! The focus restoration fix is solid and the embedded example will be valuable for future testing. 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Terminal input stops working after context menu dismissal when embedded

1 participant