Conversation
- Add fontName setting to TerminalSettings for custom system fonts - Create FontUtils.kt with font listing and loading utilities: - getCategorizedFonts(): Returns fonts organized by category - loadTerminalFont(): Loads system fonts via Skia FontMgr - Monospace detection by comparing 'W' and 'i' glyph widths - Add SettingsSectionedDropdown component for grouped font selection: - Bundled section (MesloLGS Nerd Font) - Fixed Pitch section (monospace fonts) - Variable Pitch section (proportional fonts) - Update VisualSettingsSection to use sectioned font dropdown - Refactor font loading in EmbeddableTerminal and TabbedTerminal - Update README with fontName configuration example Variable pitch fonts work with fixed cell width (same as iTerm2). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review - PR #112: Custom Font SupportGreat work on adding custom font support! This is a well-designed feature with a clean architecture. Here's my detailed review: ✅ Strengths
🔍 Issues & Concerns1. Performance: Font Scanning on Every Settings Open (Critical)Location: val categorizedFonts = remember { getCategorizedFonts() }Problem: Impact:
Solution: Cache at application level, not composition level: // In FontUtils.kt
private val categorizedFontsCache: Map<String, List<String>> by lazy {
getCategorizedFontsInternal()
}
fun getCategorizedFonts(): Map<String, List<String>> = categorizedFontsCache
private fun getCategorizedFontsInternal(): Map<String, List<String>> {
// Current implementation here
}Then val categorizedFonts = remember { getCategorizedFonts() } // Now instant2. Unsafe Non-Null Assertions (Medium)Location: categorized[FONT_SECTION_BUNDLED]!! + categorized[FONT_SECTION_FIXED_PITCH]!!Problem: Using Solution: categorized[FONT_SECTION_BUNDLED].orEmpty() + categorized[FONT_SECTION_FIXED_PITCH].orEmpty()Or better, use categorized.getValue(FONT_SECTION_BUNDLED) + categorized.getValue(FONT_SECTION_FIXED_PITCH)3. Deprecated Functions Should Not Have Active Callers (Low)Location: The Recommendation: Remove deprecated functions in this PR or document why they're needed for backward compatibility. 🎯 Suggestions4. Font Detection Tolerance Could Miss Some Monospace FontsLocation: if (kotlin.math.abs(widthW - widthI) < 0.1f) {Consideration: Some fonts marketed as monospace may have slightly unequal widths due to kerning or hinting. iTerm2 likely uses a different heuristic. Consider testing with fonts like "Input Mono" or "Iosevka" which have subtle width variations. Alternative Approach: Check if Skia's 5. Error Handling Silently Skips FontsLocation: } catch (e: Exception) {
// Skip fonts that fail to load
}Consideration: While skipping is correct behavior, debugging font issues would be easier with optional logging. Consider: } catch (e: Exception) {
if (System.getProperty("bossterm.debug.fonts") != null) {
System.err.println("Skipping font '$familyName': ${e.message}")
}
}6. Font Reloading on Settings ChangeLocation: val sharedFont = remember(settings.fontName) { loadTerminalFont(settings.fontName) }Positive: ✅ Correctly uses Question: Does this properly trigger terminal recomposition? Verify that changing the font in settings causes all visible terminals to re-render with the new font. 🧪 Testing Recommendations
📝 Documentation
🏁 VerdictOverall Assessment: This is a solid PR with good engineering practices. The critical performance issue (#1) should be fixed before merge, but the rest are minor improvements. Recommendation:
After addressing #1 and #2, this is ready to merge! 🚀 Reviewed against BossTerm conventions from CLAUDE.md |
Pull Request Review: Custom Font Support (#103)SummaryThis PR adds custom font support with an iTerm2-style font picker. The implementation is well-structured with clean separation of concerns and proper error handling. Overall, this is high-quality work with a few minor suggestions for improvement. Strengths
Issues & Suggestions1. Performance: Font Categorization on UI Thread (Medium Priority)Location: FontUtils.kt:25-60, VisualSettingsSection.kt:46 Problem: getCategorizedFonts() iterates over all system fonts (potentially 100+) and measures glyph widths for each on UI thread. Suggestion: Use coroutines for async computation or add module-level caching with lazy initialization. 2. Logic Issue: Non-Nullable Map Access (Medium Priority)Location: FontUtils.kt:72, 74-76 Problem: Force-unwrapping !! on map lookups could crash if sections are empty. Suggestion: Use safe access with elvis operator ?: to provide fallback values. 3. Monospace Detection Robustness (Low Priority)Location: FontUtils.kt:40-49 Problem: Only compares W and i characters. Some fonts might pass but have variable width. Suggestion: Test additional character pairs (M, l, 1, 0) for better accuracy. 4. UI Consistency: Empty Sections (Low Priority)Location: SettingsComponents.kt:543-576 Problem: Empty sections still show headers with no items. Suggestion: Filter out empty sections before rendering. 5. Documentation: Outdated Comment (Trivial)Location: EmbeddableTerminal.kt:37-39 Problem: Comment mentions fontPath but setting is fontName. Fix: Update comment to reference fontName. Security ReviewNo security concerns identified. No user input in file paths, uses Skia safe API, proper temp file cleanup. Test CoverageMissing automated tests. Suggest adding unit tests for font utilities and integration tests for UI. RecommendationsBefore merge:
Future improvements:
VerdictAPPROVED with minor suggestions. Solid implementation following BossTerm patterns. Issues are non-critical and can be addressed in follow-up PRs. Great work on the iTerm2-style font categorization! Generated with Claude Code |
feat: Add custom font support with iTerm2-style font picker (#103)
Summary
fontNamesetting toTerminalSettingsfor custom system fontsFontUtils.ktwith font listing and loading utilities using Skia FontMgrSettingsSectionedDropdowncomponent with iTerm2-style grouped font selectionChanges
SettingsSectionedDropdowncomponentfontNamesettingTest plan
Closes #103
🤖 Generated with Claude Code