Skip to content

feat: add components library CLI support#9

Merged
adnaan merged 4 commits intomainfrom
feature/components-library-support
Dec 9, 2025
Merged

feat: add components library CLI support#9
adnaan merged 4 commits intomainfrom
feature/components-library-support

Conversation

@adnaan
Copy link
Copy Markdown
Contributor

@adnaan adnaan commented Dec 9, 2025

Summary

Phase 1 - Simplify kits

  • Remove helpers_bulma.go and helpers_pico.go
  • Update loader.go to remove Bulma/Pico cases
  • Update simple kit to use 'none' CSS
  • Remove Bulma/Pico cases from css_helpers.go

Phase 9 - CLI commands

  • Add lvt component list - list available components
  • Add lvt component eject <name> - eject full component source
  • Add lvt component eject-template <name> <template> - eject template only
  • Add lvt new component <name> - scaffold new component
  • Add internal/eject package with eject logic
  • Update CLI help text

Context

Part of the components library implementation (Phases 1 & 9 from COMPONENTS_LIBRARY_PLAN.md).

The components library simplifies the CSS strategy to Tailwind-only, removing the need for the multi-framework abstraction layer.

Test plan

  • Eject tests (11 tests)
  • Scaffold command tests (15 tests)
  • Existing tests pass

🤖 Generated with Claude Code

Phase 1 - Simplify kits:
- Remove helpers_bulma.go and helpers_pico.go
- Update loader.go to remove Bulma/Pico cases
- Update simple kit to use 'none' CSS
- Remove Bulma/Pico cases from css_helpers.go

Phase 9 - CLI commands:
- Add 'lvt component list' - list available components
- Add 'lvt component eject' - eject full component source
- Add 'lvt component eject-template' - eject template only
- Add 'lvt new component' - scaffold new component
- Add internal/eject package with eject logic
- Update CLI help text

Part of the components library implementation (Phases 1 & 9).

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings December 9, 2025 07:10
@claude
Copy link
Copy Markdown

claude bot commented Dec 9, 2025

Pull Request Review - PR #9: feat: add components library CLI support

Overview

This PR implements Phase 1 (simplify kits) and Phase 9 (CLI commands) of the components library plan. It removes Bulma/Pico CSS framework support to simplify to Tailwind-only, and adds comprehensive CLI commands for component management.

Summary:Approved - This is a well-implemented, thoroughly tested PR that follows Go best practices. A few minor suggestions for improvement below.


Code Quality & Best Practices

✅ Strengths

  1. Excellent Code Organization

    • Clear separation of concerns: commands/ for CLI, internal/eject/ for core logic
    • Proper use of internal packages to encapsulate implementation details
    • Consistent naming conventions throughout
  2. Strong Test Coverage

    • 11 tests for eject functionality
    • 15 tests for scaffold/new component functionality
    • Tests cover edge cases (empty names, existing destinations, invalid inputs)
    • Good use of table-driven tests (e.g., TestIsValidComponentName)
  3. Good Error Handling

    • Descriptive error messages with actionable guidance
    • Proper error context propagation
    • Helpful suggestions in error messages (e.g., "Run 'lvt component list' to see available components")
  4. Clean Template Generation

    • Smart handling of hyphenated component names (e.g., date-picker → package datepicker)
    • Proper escaping in generated Go code templates
    • Both styled (Tailwind) and unstyled versions in templates
  5. Help Text & UX

    • Comprehensive help documentation for all new commands
    • Clear examples in help text
    • Good "next steps" guidance after command execution

Potential Issues & Security Concerns

⚠️ Minor Issues

  1. Command Injection Risk (Low Severity) - internal/eject/eject.go

    • Lines 226, 231, 321: Uses exec.Command("go", ...) with package names
    • While pkg comes from hardcoded list, it's good practice to validate/sanitize
    • Recommendation: Add validation to ensure package names match expected format
    // Add before exec.Command calls
    if !isValidPackageName(pkg) {
        return "", fmt.Errorf("invalid package name: %s", pkg)
    }
  2. File Permission Handling - internal/eject/eject.go:100, 206

    • Creates directories with 0755 permissions (world-readable)
    • Creates files with 0644 permissions (line 316)
    • Recommendation: This is actually fine for source code, but document the intention
  3. Import Path Replacement - internal/eject/eject.go:314

    • Uses simple strings.ReplaceAll for updating imports
    • Could accidentally replace strings in comments or string literals
    • Recommendation: Consider using golang.org/x/tools/go/ast for safer AST-based replacement
    • Status: Low priority - current approach works for the expected use case
  4. Incomplete Flag Parsing - commands/component_eject.go:31-40

    • Flag parsing doesn't handle missing value for --dest
    • Example: lvt component eject foo --dest (no value provided) will use next arg or panic
    • Recommendation:
    case "--dest", "-d":
        if i+1 >= len(args) {
            return fmt.Errorf("--dest requires a value")
        }
        opts.DestDir = args[i+1]
        i++
  5. Test File Copying - internal/eject/eject.go:267-269

    • Skips _test.go files during ejection
    • Good decision, but not documented in user-facing docs
    • Recommendation: Mention this in help text or ejection output

Performance Considerations

✅ Good Performance Practices

  1. Efficient File Operations

    • Uses io.Copy for file copying (line 302) - efficient buffered copying
    • Appropriate use of filepath.Walk for directory traversal
  2. Minimal Dependencies

    • No heavy external dependencies added
    • Uses standard library effectively

💡 Optimization Opportunities (Non-Critical)

  1. Component List Hardcoded - internal/eject/eject.go:23-46

    • 20 components hardcoded in slice
    • Not a performance issue, but requires manual sync with actual library
    • Suggestion: Consider auto-generating from actual components library in future
    • Status: Current approach is fine for now, documented clearly
  2. Multiple go list Calls

    • Line 321: GetModuleName() runs go list -m
    • Line 226: findModulePath() runs go list per component
    • Impact: Minimal - these are one-time operations during eject
    • Suggestion: Could cache module name if calling multiple times

Architecture & Design

✅ Excellent Design Choices

  1. Functional Options Pattern

    • EjectOptions and EjectTemplateOptions use clear struct-based config
    • Easy to extend without breaking changes
  2. Progressive Enhancement

    • Template-only ejection (keeps Go logic in library)
    • Full ejection (complete control)
    • Smart progression path for users
  3. Clear Boundaries

    • internal/eject is properly abstracted
    • Commands layer is thin wrapper around business logic
    • Easy to test independently

💡 Future Considerations

  1. Component Discovery
    • Current list is hardcoded - works well for stable library
    • Future: Could query actual library dynamically
    • Not needed now, but document the sync process

Testing Assessment

✅ Strong Test Coverage

  1. Unit Tests

    • eject_test.go: Tests component lookup, validation, error cases
    • new_component_test.go: Tests name validation, case conversion, file generation
    • Good coverage of edge cases
  2. Integration-Style Tests

    • Uses real filesystem with os.MkdirTemp
    • Cleans up properly with defer os.RemoveAll
    • Tests actual file creation and template rendering

💡 Test Improvement Suggestions

  1. Missing Coverage:

    • No tests for updateImports() function
    • No tests for copyDir() with nested directories
    • No tests for --force flag behavior
    • Priority: Medium - add in follow-up PR
  2. Could Add:

    • Test for component ejection with actual mock module cache
    • Test for handling of permission errors
    • Test for very long component names

Documentation & Usability

✅ Excellent Documentation

  1. Help Text

    • Clear, comprehensive help for all commands
    • Good examples included
    • Proper command hierarchy explanation
  2. Code Comments

    • Package-level documentation present
    • Exported functions documented
    • Complex logic explained
  3. User Guidance

    • Clear "next steps" after operations
    • Helpful error messages with suggestions

💡 Minor Documentation Gaps

  1. CHANGELOG/README

    • Should update main README.md with new commands (if not already done)
    • Add to CHANGELOG.md with version
  2. Component List Sync

    • Document process for keeping AvailableComponents() in sync with actual library
    • Add TODO or comment about future auto-discovery

Breaking Changes Assessment

✅ Phase 1 (Simplify Kits)

Removed:

  • helpers_bulma.go (338 lines)
  • helpers_pico.go (289 lines)
  • Bulma/Pico cases from css_helpers.go

Impact:

  • Breaking change for users expecting Bulma/Pico support
  • Properly documented in PR description
  • Simplification aligns with project direction (Tailwind-only)

Recommendation: ✅ This is intentional and well-communicated. Ensure:

  1. Migration guide exists for Bulma/Pico users (if any)
  2. Clear communication in release notes
  3. Update any external docs/tutorials

Specific Code Review Notes

internal/eject/eject.go

Line 267-269: Skipping test files is good, but undocumented

if strings.HasSuffix(path, "_test.go") {
    return nil  // Add comment: "Skip test files - users should write their own tests"
}

Line 314: Import replacement is naive but acceptable

// Current: Simple string replacement
updated := strings.ReplaceAll(string(content), oldPkg, newPkg)

// Future enhancement (if needed): Use golang.org/x/tools/go/ast
// For now, this is fine since oldPkg is specific enough

Line 94-97: Good error message with actionable guidance

return fmt.Errorf("component not found: %v\nRun 'go get %s' first", err, comp.Package)

commands/new_component.go

Line 118-132: Name validation is thorough ✅

// Good: Validates hyphens not at start/end
// Good: Rejects uppercase
// Good: Rejects special chars

Line 173-304: Template generation is clean ✅

  • Good use of raw string literals
  • Proper template escaping
  • Includes both styled and unstyled versions

internal/generator/css_helpers.go

Line 9: Good comment explaining the simplification

// Only Tailwind and None are supported - Bulma and Pico have been removed for simplification

Line 20, 32, etc.: Consistent default to Tailwind ✅


Security Audit Summary

🔒 Security Status: GOOD

  1. ✅ No SQL Injection Risk - No database operations
  2. ✅ No XSS Risk - Generates Go code, not web content
  3. ✅ Path Traversal Protection - Uses filepath.Join consistently
  4. ⚠️ Minor: Command Injection - Low risk, see recommendation above
  5. ✅ No Secret Leakage - No handling of sensitive data
  6. ✅ Proper Temp Dir Cleanup - Tests clean up properly
  7. ✅ File Permissions - Appropriate for source code (0644/0755)

Final Recommendations

Must Fix (Before Merge)

None - PR is good to merge

Should Fix (Post-Merge)

  1. Add validation for package names before exec.Command
  2. Add error handling for missing flag values (--dest without value)
  3. Add tests for updateImports() and --force flag

Nice to Have

  1. Consider AST-based import replacement (low priority)
  2. Add integration test with actual component library
  3. Document component list sync process

Conclusion

This is a high-quality PR that:

  • ✅ Follows Go best practices
  • ✅ Has comprehensive test coverage (26 new tests)
  • ✅ Includes clear documentation and help text
  • ✅ Handles errors gracefully with helpful messages
  • ✅ Makes intentional, well-communicated breaking changes
  • ✅ Uses appropriate security practices

The code is production-ready. The suggestions above are minor improvements that can be addressed in follow-up PRs.

Recommendation: APPROVE ✅


Testing Checklist for Maintainers

Before merging, verify:

  • All existing tests pass: go test ./...
  • New tests pass: go test ./internal/eject ./commands
  • Help text renders correctly: lvt component --help
  • Example commands work: lvt component list
  • README.md updated with new commands
  • CHANGELOG.md includes these changes

Review generated with ❤️ by Claude Code

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements two significant changes to the LiveTemplate CLI:

Phase 1 - CSS Framework Simplification: Removes support for Bulma and Pico CSS frameworks, standardizing on Tailwind CSS as the primary framework with "none" as the semantic HTML fallback. This simplifies the multi-framework abstraction layer that previously supported four CSS options. The changes include:

  • Deletion of helpers_bulma.go and helpers_pico.go
  • Removal of Bulma/Pico cases from loader.go and css_helpers.go
  • Update of the simple kit to use "none" CSS instead of Pico

Phase 9 - Component Library CLI Support: Adds comprehensive CLI commands for managing UI components from the components library:

  • lvt component list - Lists 20 available UI components with their templates
  • lvt component eject <name> - Ejects full component source code to the user's project
  • lvt component eject-template <name> <template> - Ejects only template files while keeping Go logic in the library
  • lvt new component <name> - Scaffolds new custom components with proper structure

The implementation includes a new internal/eject package for component ejection logic, comprehensive test coverage (26 tests across eject and scaffold commands), and updates to documentation and help text.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
main.go Added component command routing and updated help text
internal/kits/system/simple/kit.yaml Changed CSS framework from "pico" to "none"
internal/kits/loader.go Removed Bulma/Pico cases, added explanatory comment
internal/kits/helpers_pico.go Deleted file (289 lines)
internal/kits/helpers_bulma.go Deleted file (338 lines)
internal/generator/css_helpers.go Removed all Bulma/Pico template helper functions
internal/eject/eject_test.go Added 11 comprehensive tests for eject functionality
internal/eject/eject.go New package implementing component ejection with 327 lines
commands/new_component_test.go Added 15 tests for component scaffolding
commands/new_component.go New command implementing component scaffolding (304 lines)
commands/new.go Added component subcommand routing before help check
commands/mcp_server.go Updated MCP schema to reflect tailwind/none only
commands/help.go Added 106 lines of help text for component commands
commands/component_eject_template.go New command for ejecting templates only
commands/component_eject.go New command for ejecting full components
commands/component.go New command group handler with component list
docs/MCP_TOOLS.md Updated CSS options from "tailwind|bulma|pico|none" to "tailwind|none"
docs/AGENT_USAGE_GUIDE.md Updated CSS framework documentation
commands/claude_resources/skills/new-app/SKILL.md Updated kit description (simple now uses "no CSS")
commands/claude_resources/skills/customize/SKILL.md Removed Bulma/Pico examples
commands/claude_resources/skills/TESTING.md Updated kit descriptions
README.md Updated generated template comments from Bulma to Tailwind

Comment thread internal/eject/eject.go Outdated
Comment on lines +313 to +314
// Simple string replacement for imports
updated := strings.ReplaceAll(string(content), oldPkg, newPkg)
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simple string replacement approach for updating imports is risky and could cause false positives. For example, if the old package path appears in a comment, string literal, or as part of a different package name, it will be incorrectly replaced. Consider using Go's AST parsing (golang.org/x/tools/go/ast/astutil.RewriteImport) for more accurate import path updates, or at minimum add word boundary checks to only replace imports within import statements.

Copilot uses AI. Check for mistakes.
Comment thread commands/component.go Outdated
Comment on lines +43 to +68
components := []struct {
name string
description string
templates []string
}{
{"accordion", "Collapsible content sections", []string{"lvt:accordion:default:v1", "lvt:accordion:single:v1"}},
{"autocomplete", "Search with suggestions", []string{"lvt:autocomplete:default:v1"}},
{"breadcrumbs", "Navigation breadcrumb trail", []string{"lvt:breadcrumbs:default:v1"}},
{"datatable", "Data tables with sorting/pagination", []string{"lvt:datatable:default:v1"}},
{"datepicker", "Date selection", []string{"lvt:datepicker:single:v1", "lvt:datepicker:range:v1", "lvt:datepicker:inline:v1"}},
{"drawer", "Slide-out panels", []string{"lvt:drawer:default:v1"}},
{"dropdown", "Dropdown menus", []string{"lvt:dropdown:default:v1", "lvt:dropdown:searchable:v1", "lvt:dropdown:multi:v1"}},
{"menu", "Navigation menus", []string{"lvt:menu:default:v1", "lvt:menu:nested:v1"}},
{"modal", "Modal dialogs", []string{"lvt:modal:default:v1", "lvt:modal:confirm:v1", "lvt:modal:sheet:v1"}},
{"popover", "Rich content popovers", []string{"lvt:popover:default:v1"}},
{"progress", "Progress indicators", []string{"lvt:progress:default:v1", "lvt:progress:circular:v1", "lvt:progress:spinner:v1"}},
{"rating", "Star ratings", []string{"lvt:rating:default:v1"}},
{"skeleton", "Loading placeholders", []string{"lvt:skeleton:default:v1", "lvt:skeleton:avatar:v1", "lvt:skeleton:card:v1"}},
{"tabs", "Tab navigation", []string{"lvt:tabs:horizontal:v1", "lvt:tabs:vertical:v1", "lvt:tabs:pills:v1"}},
{"tagsinput", "Tag/chip input", []string{"lvt:tagsinput:default:v1"}},
{"timeline", "Event timelines", []string{"lvt:timeline:default:v1"}},
{"timepicker", "Time selection", []string{"lvt:timepicker:default:v1"}},
{"toast", "Toast notifications", []string{"lvt:toast:default:v1", "lvt:toast:container:v1"}},
{"toggle", "Toggle switches", []string{"lvt:toggle:default:v1", "lvt:toggle:checkbox:v1"}},
{"tooltip", "Tooltips", []string{"lvt:tooltip:default:v1"}},
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component list is duplicated between commands/component.go and internal/eject/eject.go. The list in component.go uses fully qualified template names (e.g., "lvt:accordion:default:v1") while eject.go uses just the variant names (e.g., "default", "single"). This duplication creates a maintenance burden - any new component must be added to both lists, and the template names are formatted differently.

Consider using eject.AvailableComponents() directly in the ComponentList function and formatting the template names there. This would ensure a single source of truth and reduce the risk of the lists becoming out of sync.

Copilot uses AI. Check for mistakes.
Comment thread internal/eject/eject.go
defer dstFile.Close()

_, err = io.Copy(dstFile, srcFile)
return err
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyFile function doesn't preserve file permissions from the source file. It uses os.Create which creates files with default permissions (0666 minus umask). For executable files or files with specific permission requirements, this could be problematic.

Consider using os.OpenFile with the source file's permissions, or call os.Chmod after creating the file to match the source permissions. Example:

srcInfo, _ := os.Stat(src)
// ... after creating dstFile ...
dstFile.Chmod(srcInfo.Mode())
Suggested change
return err
if err != nil {
return err
}
// Preserve file permissions from source
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if err := os.Chmod(dst, srcInfo.Mode()); err != nil {
return err
}
return nil

Copilot uses AI. Check for mistakes.
Comment thread commands/new_component.go
Comment on lines +114 to +133
func isValidComponentName(name string) bool {
if name == "" {
return false
}
for i, r := range name {
if r == '-' {
if i == 0 || i == len(name)-1 {
return false
}
continue
}
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
return false
}
if unicode.IsUpper(r) {
return false
}
}
return true
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The validation allows consecutive hyphens (e.g., "date--picker" is considered valid), which could lead to awkward package/type names. While this is syntactically valid, it creates inconsistent naming. Consider adding a check to reject consecutive hyphens for better naming consistency:

if i > 0 && name[i-1] == '-' {
    return false // Reject consecutive hyphens
}

This would prevent component names like "button--group" which would become package "buttongroup" and type "ButtonGroup", making the double hyphen in the original name confusing.

Copilot uses AI. Check for mistakes.
Comment thread commands/new_component.go
Comment on lines +138 to +140
if len(word) > 0 {
words[i] = strings.ToUpper(word[:1]) + word[1:]
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a component name contains consecutive hyphens (e.g., "date--picker"), the toPascalCase function will produce incorrect results. When split by "-", this creates an empty string element, and word[:1] will panic with "index out of range" when word is empty.

While the validation currently allows consecutive hyphens (see line 31 in new_component_test.go), this creates a crash risk. Either:

  1. Add validation to reject consecutive hyphens in isValidComponentName, or
  2. Add a check in toPascalCase: if len(word) == 0 { continue }

The test on line 31 of new_component_test.go notes this is "technically valid but ugly", suggesting it should probably be rejected in validation.

Suggested change
if len(word) > 0 {
words[i] = strings.ToUpper(word[:1]) + word[1:]
}
if len(word) == 0 {
continue
}
words[i] = strings.ToUpper(word[:1]) + word[1:]

Copilot uses AI. Check for mistakes.
- Use eject.AvailableComponents() as single source of truth for component list
- Improve updateImports() to only replace within import statements
  (avoids false positives in comments/strings)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Dec 9, 2025

PR Review: Components Library CLI Support

This PR implements Phase 1 (CSS simplification) and Phase 9 (component CLI commands). Overall: Well-implemented, thoroughly tested, production-ready code.

✅ Strengths

Code Organization: Clean separation with internal/eject package, well-structured commands

Test Coverage: 26 total tests (11 eject + 15 scaffold) covering edge cases and errors

User Experience: Clear error messages, helpful output, --help flags, --force protection

Documentation: Updated README, comprehensive help text, good inline comments

🔍 Key Findings

Good Practices Found:

  • Validation prevents flag-like args as component names (component_eject.go:24)
  • Centralized component list (single source of truth)
  • Safe import updates (only within import blocks, verifies quotes)
  • Proper template generation with text/template
  • Smart file filtering (skips hidden/test files)

Minor Issues (Medium Priority):

1. File Close Error Handling (internal/eject/eject.go:295-303)
defer dstFile.Close() doesn't check errors. Add explicit sync and close checking.

2. Consecutive Hyphen Validation (commands/new_component.go:114-133)
Allows date--picker. Consider rejecting consecutive hyphens.

3. Command Safety (internal/eject/eject.go:226, 353)
exec.Command with pkg variable is currently safe (hardcoded list) but add validation if ever made dynamic.

🔒 Security: No Critical Issues

  • Path traversal: Well-handled ✅
  • File permissions: Appropriate (0755/0644) ✅
  • Input validation: Good ✅
  • Overwrite protection: --force flag ✅

Minor: exec.Command safe now, harden if sources become user-configurable

📊 Performance: Efficient Design

  • Uses Go module cache
  • Streaming copy (io.Copy)
  • Single-pass import updates

Consider: Progress indicators for large component copies

🧪 Test Coverage: Excellent

Covers: component lookup, validation, file ops, error conditions, name validation (15 cases), case conversions

Missing (acceptable edge cases): module cache integration, network failures, disk full

🎯 Recommendations

High Priority: None - no critical issues

Medium Priority:

  1. Improve file close error handling
  2. Add consecutive hyphen validation
  3. Add package path validation for future safety

Low Priority:

  1. Use stdlib strings.Contains vs custom (eject_test.go:389)
  2. Add progress indicators
  3. More doc examples

🎉 Conclusion

Production-ready PR:

  • ✅ Go best practices
  • ✅ 26 comprehensive tests
  • ✅ Clear, maintainable code
  • ✅ Excellent UX
  • ✅ Good documentation
  • ✅ No security vulnerabilities

Recommendation: ✅ APPROVE

Great work! Solid foundation for the components ecosystem.

Review focus: code quality, best practices, security, performance, test coverage, maintainability

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Dec 9, 2025

PR Review: Components Library CLI Support

Overview

This is a solid implementation that adds CLI commands for component management and simplifies the CSS framework strategy. The code quality is high with good test coverage.

✅ Strengths

1. Well-Structured Code

  • Clean separation of concerns: The internal/eject package is well-isolated with clear responsibilities
  • Single source of truth: Good use of eject.AvailableComponents() to centralize component metadata
  • Proper error handling: Comprehensive error messages with helpful hints (e.g., "Run 'lvt component list'")

2. Excellent Test Coverage

  • 11 eject tests covering edge cases (unknown components, existing destinations, force flag)
  • 15 scaffold tests including validation, name conversion, and file generation
  • Tests verify both happy paths and error cases
  • Good use of temporary directories in tests

3. User Experience

  • Helpful CLI help messages with examples
  • Clear output showing ejected files and next steps
  • Progressive validation (component exists → template exists → file conflicts)
  • Sensible defaults (e.g., internal/components/ for ejected components)

4. Documentation

  • Good package-level documentation
  • Inline comments explaining non-obvious logic (e.g., import statement detection in updateImports)
  • Generated component scaffolds include usage examples

🔍 Code Quality Observations

Good Practices

  1. Import path updates (eject.go:308-349): The updateImports() function correctly scopes replacements to import statements only, avoiding false positives in comments/strings
  2. Name conversion functions: Proper handling of hyphenated names (e.g., date-picker → package datepicker, struct DatePicker)
  3. File filtering: Correctly skips test files and hidden files when ejecting (eject.go:267-269)

Minor Issues

1. Missing Validation for --dest Flag

File: commands/component_eject.go:35-40

The --dest flag doesn't validate if the next argument exists:

case "--dest", "-d":
    if i+1 < len(args) {
        opts.DestDir = args[i+1]
        i++
    }

Issue: If user runs lvt component eject dropdown --dest (without path), it silently uses default instead of showing an error.

Suggestion: Add error handling:

case "--dest", "-d":
    if i+1 >= len(args) {
        return fmt.Errorf("--dest flag requires a path argument")
    }
    opts.DestDir = args[i+1]
    i++

Same issue in component_eject_template.go:55-60.

2. Potential Race Condition in Import Updates

File: internal/eject/eject.go:111-120

Import updates happen after files are copied, but errors are only warned about:

if err := updateImports(file, comp.Package, newPkg); err != nil {
    fmt.Printf("Warning: failed to update imports in %s: %v\n", file, err)
}

Concern: Users might miss warnings and have broken imports. Consider:

  • Collecting errors and returning them
  • Making it more prominent (stderr instead of stdout)
  • Or accepting it's non-critical since users need to manually update anyway

3. Hardcoded Component List

File: internal/eject/eject.go:23-46

The component list is hardcoded. As the library grows, this could become a maintenance burden.

Future consideration: Could potentially fetch this dynamically from the components repo API or a registry file. Not a blocker for this PR, but worth noting for future iterations.

🛡️ Security Considerations

✅ Good Security Practices

  1. No command injection: Uses exec.Command with separate args, not shell execution
  2. Path traversal prevention: Uses filepath.Join and validates relative paths
  3. File permissions: Correctly sets 0755 for directories, 0644 for files

⚠️ Minor Concern: Arbitrary Path Writing

File: internal/eject/eject.go:99-102

The --dest flag allows writing to any directory the user has permissions for. This is likely fine since it's a CLI tool run by developers, but could be surprising.

Suggestion: Consider adding a sanity check that destination is within the project (optional):

cwd, _ := os.Getwd()
absDestDir, _ := filepath.Abs(destDir)
if !strings.HasPrefix(absDestDir, cwd) {
    fmt.Printf("Warning: ejecting outside current directory: %s\n", absDestDir)
    // Ask for confirmation or proceed
}

🔧 Performance

No performance concerns. File operations are straightforward and appropriate for CLI usage.

📝 Suggestions for Follow-up (Not Blockers)

  1. Bash completion: Consider adding shell completion for component names
  2. Dry-run mode: Add a --dry-run flag to show what would be ejected without doing it
  3. Component validation: Consider validating ejected components compile (run go build in dest dir)
  4. Progress indicators: For large components, show progress during file copy

Phase 1 Changes (Simplification)

The removal of Bulma and Pico CSS helpers is clean and well-executed:

  • ✅ Removed helpers_bulma.go and helpers_pico.go entirely
  • ✅ Updated css_helpers.go to only support Tailwind and "none"
  • ✅ Updated documentation and help text consistently
  • ✅ No orphaned references found

🎯 Final Verdict

Recommendation: APPROVE with minor suggestions

This PR successfully implements Phases 1 & 9 from the components library plan. The code is production-ready with:

  • ✅ Solid architecture and code organization
  • ✅ Comprehensive test coverage (26 tests)
  • ✅ Good error handling and user experience
  • ✅ Clean removal of deprecated CSS frameworks

The minor issues noted above are suggestions for improvement, not blockers. The code follows Go best practices and the repository's contributing guidelines.

Great work on this implementation! 🎉


Review conducted by Claude Code using repository conventions from CONTRIBUTING.md

The function now correctly replaces both:
- Exact matches: "oldPkg" -> "newPkg"
- Subpackage imports: "oldPkg/internal" -> "newPkg/internal"

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Dec 9, 2025

PR Review: Components Library CLI Support

Summary

This PR successfully implements Phase 1 (simplifying kits) and Phase 9 (CLI commands) from the components library plan. The implementation is well-structured, thoroughly tested, and follows Go best practices.

✅ Strengths

Code Quality

  • Excellent test coverage: 11 tests for eject functionality, 15 tests for scaffold commands
  • Clean separation of concerns: internal/eject package is well-isolated and reusable
  • Comprehensive error handling: Clear error messages with helpful hints for users
  • Good documentation: Help text is clear and examples are provided

Architecture

  • Single source of truth: AvailableComponents() is used consistently across commands
  • Flexible design: EjectOptions allows for custom destinations and force overwriting
  • Import path updates: Smart handling of package import rewrites when ejecting components
  • Template generation: Well-structured scaffolding with proper Go naming conventions

User Experience

  • Helpful output: Clear feedback with emoji indicators and next steps
  • Sensible defaults: internal/components/<name> for full ejects, internal/templates/ for template-only
  • Safety first: Checks for existing files before overwriting (unless --force is used)

🔍 Observations & Minor Issues

1. Import Update Logic (eject.go:306-354)

The updateImports() function is well-implemented but could have edge cases:

Potential issue: Line-by-line parsing might miss imports in multiline import blocks with comments interspersed.

import (
    // This is a comment
    "github.com/livetemplate/components/dropdown"
    /* multiline comment */ "other/package"
)

Recommendation: Consider using go/parser and go/ast for more robust AST-based import rewriting. However, the current implementation is likely sufficient for 99% of cases.

2. Module Cache Discovery (eject.go:223-243)

The findModulePath() function tries two commands but doesn't provide guidance if the module isn't cached.

Current behavior:

component not found in module cache: package not found: github.com/livetemplate/components/dropdown
Run 'go get github.com/livetemplate/components/dropdown' first

Improvement suggestion: Consider automatically running go get if the package isn't found (with user confirmation), or check go.mod to see if the dependency is declared but not downloaded.

3. Test File Skipping (eject.go:267-268)

Test files are skipped during ejection, which makes sense for production code but might be undesirable if users want to see example tests.

Recommendation: Consider adding a --include-tests flag for users who want the full test suite included.

4. Component Name Validation (new_component.go:114-133)

The validation is good but isValidComponentName() allows double hyphens (e.g., date--picker), which while technically valid, is ugly.

if r == '-' {
    if i == 0 || i == len(name)-1 {
        return false
    }
    continue  // Doesn't check for consecutive hyphens
}

Recommendation: Add check for consecutive hyphens.

5. Error Messages Could Include More Context

In component_eject_template.go:21-28, when showing available templates, it would be helpful to also show template descriptions.

Current:

Available templates for dropdown:
  - default
  - searchable
  - multi

Suggestion:

Available templates for dropdown:
  - default     (Standard dropdown menu)
  - searchable  (Dropdown with search filtering)
  - multi       (Multiple selection dropdown)

🔒 Security Considerations

✅ Good Security Practices

  1. No command injection: Uses exec.Command with separate arguments
  2. Path validation: Checks for flag-like positional args with ValidatePositionalArg()
  3. File permissions: Uses appropriate 0755 for directories, 0644 for files
  4. No unsafe file operations: Uses standard library functions correctly

⚠️ Minor Security Notes

  1. File overwriting: The --force flag overwrites without confirmation - consider adding a confirmation prompt for interactive use
  2. Module cache trust: Assumes module cache contents are safe - this is reasonable as Go's module system already validates checksums

🚀 Performance Considerations

✅ Efficient Implementation

  • Minimal allocations: String operations are well-optimized
  • Single-pass file walking: filepath.Walk is used efficiently
  • No unnecessary file reads: Only reads what's needed

Minor Optimization Opportunities

  1. Parallel file copying: For large components with many files, consider using goroutines (though probably overkill for this use case)
  2. Import update batching: Multiple files could potentially be updated in parallel

📝 Documentation & Help Text

✅ Excellent

  • All commands have comprehensive --help output
  • Examples are provided for each command
  • Help text follows consistent formatting
  • Instructions for contributing components to the official library

Suggestions

  1. Consider adding a COMPONENTS.md guide in the docs folder
  2. Add examples to README.md showing the full workflow

🧪 Test Coverage

✅ Comprehensive Testing

  • Edge cases covered: empty names, invalid names, existing destinations
  • Error paths tested: Unknown components, unknown templates, validation failures
  • Helper functions tested: Case conversion, package naming, validation logic
  • File operations tested: Copy operations, directory creation

Minor Gaps

  1. Integration tests: Consider adding an end-to-end test that actually ejects a real component from the library
  2. Template rendering: The generated component templates aren't tested for Go compilation
  3. Import updates: Could add more tests for edge cases in updateImports()

🎯 Alignment with Conventions

Based on CONTRIBUTING.md and the cursor rules:

✅ Follows Conventions

  • Standard Go formatting (go fmt)
  • Meaningful variable names
  • Focused functions
  • Explicit error handling
  • Conventional commit message format
  • Tests included for all new functionality

Project Structure

New files are appropriately located:

  • internal/eject/ - Clean, isolated package ✅
  • commands/component*.go - Follows existing command pattern ✅
  • Help functions in commands/help.go

🔄 Breaking Changes & Migration

Phase 1: Kit Simplification

Removed: helpers_bulma.go, helpers_pico.go

Impact: Users with existing apps using Bulma/Pico kits will need to:

  1. Update their projects to use Tailwind or semantic HTML
  2. Regenerate resources with new CSS framework

Recommendation: Add a migration guide or script to help users transition existing projects.

🎨 Code Style & Readability

✅ Excellent

  • Clear function and variable names
  • Good code organization
  • Appropriate comments
  • Consistent error message formatting
  • Template literals are well-escaped

Minor Suggestions

  1. The template strings in new_component.go are very long (lines 173-304) - consider moving to separate files or embed.FS
  2. Some functions could benefit from additional godoc comments explaining parameters

📊 Overall Assessment

Scoring (1-5, 5 being excellent)

Category Score Notes
Code Quality 5/5 Clean, well-organized, follows best practices
Test Coverage 5/5 Comprehensive unit tests, good edge case coverage
Documentation 4/5 Great help text, could use more docs
Error Handling 5/5 Clear messages with helpful guidance
Security 5/5 No security concerns identified
Performance 5/5 Efficient implementation
Architecture 5/5 Well-designed, maintainable
User Experience 5/5 Intuitive commands with helpful output

Overall: 4.9/5 ⭐⭐⭐⭐⭐

✅ Recommendation

APPROVE - This is production-ready code. The minor suggestions above are enhancements, not blockers.

Suggested Action Items (Optional, Post-Merge)

  1. Add integration test for actual component ejection
  2. Consider adding --include-tests flag for ejection
  3. Improve component name validation to reject consecutive hyphens
  4. Add migration guide for Bulma/Pico removal
  5. Move long template strings to embed.FS for better maintainability

🎉 Conclusion

Excellent work on this PR! The implementation is solid, well-tested, and provides a great user experience. The components library support will be a valuable addition to the lvt CLI.


Review generated by Claude Code

@adnaan adnaan merged commit 1598a0c into main Dec 9, 2025
2 checks passed
@adnaan adnaan deleted the feature/components-library-support branch December 9, 2025 23:24
adnaan added a commit that referenced this pull request Feb 25, 2026
- Document why kit is set both in inputs map and via SetKit()
- Merge Apply_NoArgs and Apply_ReturnsNotImplemented into single
  Apply_NotImplemented test since the stub ignores args

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
adnaan added a commit that referenced this pull request Feb 25, 2026
* feat: Milestone 3 — Telemetry & Evolution (Issues #61#67)

Add self-improving evolution system that captures generation events,
matches errors to known patterns, proposes fixes, and tests them in
isolation. This creates a feedback loop: capture → match → propose → test.

New packages:
- internal/telemetry: SQLite-backed event store with noop-safe Collector/Capture API
- internal/evolution/knowledge: Markdown parser for evolution/patterns.md (13 patterns)
- internal/evolution: Fix proposer, tester, and upstream support

Integration:
- commands/gen.go, commands/auth.go: Telemetry capture around all generators
- commands/evolution.go: CLI commands (status, metrics, failures, patterns, propose, apply)
- main.go: Route "evolution"/"evo" command

Closes #61, #62, #63, #64, #65, #66, #67

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: fix gofmt formatting in evolution.go and events.go

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address bot review comments on PR #130

- Change telemetry to opt-in (LVT_TELEMETRY=true) instead of opt-out
- Fix double validation: refactor runPostGenValidation to return result
  and reuse marshalValidationResult instead of re-running validation
- Parameterize LIMIT query in SQLite store
- Fix scanInto to return errors on parse/unmarshal failures
- Add getLvtVersion() using debug.ReadBuildInfo()
- Remove unused started field from Capture
- Fix tester glob to match relative paths, not just filenames
- Fix proposer dedup key to include FindPattern+Replace
- Add kit index to schema.sql
- Fix zero-total percentage display in evolution status
- Gate apply command (return not-implemented error)
- Fix truncate() to use rune count instead of byte length
- Sort metrics output by command name
- Remove runtime.Caller(0) fallback from FindPatternsFile
- Walk up directory tree from cwd to find patterns.md
- Document evo alias in main.go usage
- Fix TestEvolution_Patterns to use t.Skipf
- Add upstream proposer unit tests
- Add NewFromPatterns constructor for testing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address second round of bot review comments

- Simplify NewCollector() to return *Collector (always non-nil, no error)
- Remove startCapture helper and nil guards at all call sites
- Remove dead filepath.Match call in tester.go Walk fallback
- Mark 'apply' command as [coming soon] in help text
- Make patterns.md integration tests structural (not exact counts/order)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: defer capture.Complete in auth.go to after resource-protection

Move telemetry capture completion to after all post-gen work so the
event correctly reflects the full outcome of the command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use deferred closure for capture.Complete in auth.go

Track success with a local bool so the deferred capture.Complete
correctly reflects late failures. Also relax upstream pattern count
assertion to >= 1 instead of exact count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: call capture.Complete directly at end of Auth()

Replace deferred closure with direct call before return nil, matching
the pattern used in gen.go. Also document the */filename glob
constraint in FixTemplate.File.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: fix gofmt alignment in types.go

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Claude review #7 — proposer loop, glob/walk, upstream test

- Move ListPatterns() call before error loop in proposer (single lock acquire)
- Skip Glob for "*/"-prefixed targets, go straight to Walk for consistency
- Use strings.CutPrefix instead of HasPrefix+TrimPrefix
- Fix TestUpstreamProposer_NoUpstreamRepo to set MessageRe so it tests
  the UpstreamRepo="" path, not the MessageRe==nil guard
- Add TODO comment in upstream.go for first-fix-only limitation
- Simplify evolutionApply to use _ for unused args parameter
- Extract defaultLookbackDays const for 30-day hardcoding

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Claude review #8 — minor polish items

- Add TODO for unwired RecordFileGenerated method
- Align null-guard for inputsJSON to match errorsJSON/filesJSON
- Document context regex message fallback rationale in matcher.go

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Claude review #9 — kit comment, apply test naming

- Document why kit is set both in inputs map and via SetKit()
- Merge Apply_NoArgs and Apply_ReturnsNotImplemented into single
  Apply_NotImplemented test since the stub ignores args

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address Claude review #10 — surface read errors, prevent dup inserts

- Return error instead of silently skipping unreadable files in applyFix
- Use INSERT OR IGNORE to handle unlikely ID collisions gracefully

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: preserve file permissions in applyFix, document INSERT OR IGNORE

- Use os.Stat to capture original file mode before rewriting
- Add comment explaining why INSERT OR IGNORE is used

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: XDG_CONFIG_HOME isolation in tests, consistent propose behavior

- Clear XDG_CONFIG_HOME in all evolution tests that set HOME for isolation
- Make evolutionPropose print-and-return-nil when disabled, matching other subcommands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: mark unused args parameters with _ in evolution subcommands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants