Documentation
¶
Overview ¶
Package devtools provides a comprehensive developer tools system for debugging and inspecting BubblyUI applications in real-time.
The dev tools provide component tree visualization, reactive state inspection, event tracking, route debugging, performance monitoring, and command timeline analysis. Tools integrate seamlessly with running TUI applications through a split-pane interface or separate inspection window.
Basic Usage ¶
// Enable dev tools globally
devtools.Enable()
// Toggle visibility (F12 shortcut typically)
dt := devtools.Enable()
dt.ToggleVisibility()
// Check if enabled
if devtools.IsEnabled() {
// Dev tools are active
}
Thread Safety ¶
All functions and methods in this package are thread-safe and can be called concurrently from multiple goroutines.
Performance ¶
Dev tools overhead is < 5% when enabled and zero when disabled. The system uses lazy initialization and efficient data structures to minimize impact on application performance.
Package devtools provides comprehensive developer tools for debugging and inspecting BubblyUI applications in real-time.
Overview ¶
The dev tools system offers a complete debugging solution for TUI applications:
- Component Inspector: Hierarchical tree view with state inspection
- State Viewer: Real-time reactive state tracking with history
- Event Tracker: Event capture, filtering, and replay
- Performance Monitor: Render timing, flame graphs, and metrics
- Export/Import: Debug session persistence with compression
- Sanitization: PII/PCI/HIPAA pattern templates for safe sharing
- Framework Hooks: Reactive cascade visualization
Quick Start ¶
Enable dev tools with a single line:
import "github.com/newbpydev/bubblyui/pkg/bubbly/devtools"
func main() {
devtools.Enable() // Zero-config enablement
app := createMyApp()
tea.NewProgram(app, tea.WithAltScreen()).Run()
}
Core API ¶
The main entry points are package-level functions operating on a global singleton:
devtools.Enable() // Create and activate dev tools devtools.Disable() // Stop data collection and cleanup devtools.Toggle() // Toggle enabled state devtools.IsEnabled() // Check if dev tools are active
Visibility control (UI display):
dt := devtools.Enable() dt.SetVisible(true) // Show dev tools UI dt.ToggleVisibility() // Toggle UI visibility dt.IsVisible() // Check if UI is shown
Component Inspector ¶
Inspect component hierarchy and state:
inspector := devtools.GetComponentInspector()
comp := inspector.FindByName("Counter")
// View component details
state := comp.GetState() // All refs and computed values
props := comp.GetProps() // Component properties
children := comp.GetChildren() // Child components
The inspector provides:
- Hierarchical tree view with expand/collapse
- Component selection and highlighting
- State inspection (refs, computed, watchers)
- Props and metadata display
- Search and filtering
- Live updates as tree changes
State Viewer ¶
Track reactive state changes over time:
viewer := devtools.GetStateViewer()
viewer.SelectRef("count")
history := viewer.GetHistory() // All value changes with timestamps
Features:
- All reactive state in application
- Ref values with type information
- State history tracking (circular buffer)
- State editing for testing
- Dependency graph visualization
Event Tracking ¶
Capture and analyze events:
tracker := devtools.GetEventTracker()
tracker.Pause() // Temporarily pause capture
tracker.Resume() // Resume capture
// Get recent events
events := tracker.GetRecent(50)
// Filter events
filter := tracker.SetFilter("click")
Event system features:
- Real-time event capture
- Event name, source, target, payload
- Event handler execution trace
- Event bubbling path
- Timing information
- Filtering and search
- Replay capability
Performance Monitoring ¶
Track component render performance:
perfMon := devtools.GetPerformanceMonitor()
stats := perfMon.GetComponentStats("Counter")
fmt.Printf("Avg render: %v\n", stats.AvgRenderTime)
fmt.Printf("Max render: %v\n", stats.MaxRenderTime)
fmt.Printf("Total renders: %d\n", stats.RenderCount)
Performance features:
- Component render timing
- Update cycle duration
- Lifecycle hook timing
- Memory usage per component
- Slow operation detection
- Flame graph generation
- Timeline visualization
Export and Import ¶
Share debug sessions with compression and sanitization:
// Export with compression
devtools.Export("debug-session.json.gz", devtools.ExportOptions{
Compress: true,
CompressionLevel: gzip.BestCompression,
IncludeState: true,
IncludeEvents: true,
Sanitize: sanitizer,
})
// Import (auto-detects format and compression)
devtools.Import("debug-session.json.gz")
Export formats supported:
- JSON: Universal, human-readable
- YAML: Most readable, configuration tools
- MessagePack: Smallest, fastest
Compression levels:
- gzip.BestSpeed: ~50% reduction, fast
- gzip.DefaultCompression: ~60% reduction, balanced
- gzip.BestCompression: ~70% reduction, maximum
Sanitization ¶
Remove sensitive data before sharing exports:
// Use built-in templates
sanitizer := devtools.NewSanitizer()
sanitizer.LoadTemplates("pii", "pci", "hipaa")
// Add custom patterns with priority
sanitizer.AddPatternWithPriority(
`(?i)(api[_-]?key)(["'\s:=]+)([^\s"']+)`,
"${1}${2}[REDACTED]",
80, // High priority
"api_key",
)
// Preview before applying (dry-run)
result := sanitizer.Preview(exportData)
fmt.Printf("Would redact %d values\n", result.WouldRedactCount)
// Sanitize for real
cleanData := sanitizer.Sanitize(exportData)
Built-in templates:
- "pii": SSN, email, phone (GDPR, CCPA)
- "pci": Credit cards, CVV, expiry dates
- "hipaa": Medical records, diagnoses
- "gdpr": IP addresses, MAC addresses
Priority system ensures complex rules apply correctly (higher priority = first).
Framework Hooks ¶
Integrate custom instrumentation with the reactive cascade:
type MyHook struct{}
func (h *MyHook) OnComponentMount(id, name string) {
fmt.Printf("Mounted: %s (%s)\n", name, id)
}
func (h *MyHook) OnRefChange(id string, oldVal, newVal interface{}) {
fmt.Printf("Ref %s: %v → %v\n", id, oldVal, newVal)
}
func (h *MyHook) OnComputedChange(id string, oldVal, newVal interface{}) {
fmt.Printf("Computed %s: %v → %v\n", id, oldVal, newVal)
}
func (h *MyHook) OnWatchCallback(watcherID, refID string, newVal, oldVal interface{}) {
fmt.Printf("Watch %s triggered by %s\n", watcherID, refID)
}
// Register the hook
hook := &MyHook{}
devtools.RegisterHook(hook)
Framework hooks track the complete reactive cascade:
- Component lifecycle (mount, update, unmount)
- Ref changes (Set operations)
- Computed changes (re-evaluation with new values)
- Watch callbacks (observer notifications)
- WatchEffect executions (automatic dependency tracking)
- Component tree mutations (AddChild, RemoveChild)
This enables visualization of data flow: Ref → Computed → Watchers → Effects
Incremental Exports ¶
For long-running applications, use incremental exports to track changes:
// Day 1: Full snapshot (125 MB)
checkpoint := devtools.ExportFull("day-1.json", opts)
// Day 2: Only changes (8 MB)
checkpoint = devtools.ExportIncremental("day-2-delta.json", checkpoint)
// Day 3: Only changes (7 MB)
checkpoint = devtools.ExportIncremental("day-3-delta.json", checkpoint)
// Reconstruct timeline
devtools.Import("day-1.json")
devtools.ImportDelta("day-2-delta.json")
devtools.ImportDelta("day-3-delta.json")
This saves 93% storage for long sessions (8MB vs 125MB daily).
Streaming Mode ¶
Handle large exports (>100MB) without memory issues:
err := devtools.ExportStream("large-export.json", devtools.ExportOptions{
IncludeState: true,
IncludeEvents: true,
UseStreaming: true,
ProgressCallback: func(bytes int64) {
mb := bytes / 1024 / 1024
fmt.Printf("\rProcessed: %d MB", mb)
},
})
Streaming guarantees:
- Constant memory usage (bounded by buffer size)
- Progress reporting for long operations
- No OOM errors regardless of export size
- Suitable for CI/CD debug logs
Configuration ¶
Configure dev tools via code or environment variables:
config := devtools.DefaultConfig() config.LayoutMode = devtools.LayoutHorizontal // Side-by-side config.SplitRatio = 0.60 // 60/40 app/tools config.MaxComponents = 10000 config.MaxEvents = 5000 config.SamplingRate = 1.0 // 100% sampling
Environment variables:
BUBBLY_DEVTOOLS_ENABLED=true BUBBLY_DEVTOOLS_LAYOUT_MODE=horizontal BUBBLY_DEVTOOLS_SPLIT_RATIO=0.60 BUBBLY_DEVTOOLS_MAX_COMPONENTS=10000 BUBBLY_DEVTOOLS_MAX_EVENTS=5000
Performance Characteristics ¶
Dev tools are designed for minimal overhead:
- < 5% performance impact when enabled
- Zero impact when disabled
- < 50ms render time for dev tools UI
- < 10ms state update latency
- < 100ms search operations
- < 50MB memory overhead
Thread Safety ¶
All package-level functions and types are thread-safe:
- Singleton initialization uses sync.Once
- All mutations protected by sync.RWMutex
- Copy-on-read patterns prevent data races
- Hook execution isolated from application
Integration with Bubbletea ¶
Dev tools integrate seamlessly with Bubbletea applications:
type model struct {
component bubbly.Component
}
func (m model) Init() tea.Cmd {
// Dev tools track Init() via hooks
return m.component.Init()
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Dev tools track all messages
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "f12" {
devtools.Toggle() // Toggle dev tools
}
}
updated, cmd := m.component.Update(msg)
m.component = updated.(bubbly.Component)
return m, cmd
}
func (m model) View() string {
// Dev tools measure render time
return m.component.View()
}
Error Handling ¶
Dev tools never crash the host application:
- Panics in hooks are recovered and reported
- Errors in data collection are logged but not fatal
- Graceful degradation on memory limits
- Application continues even if dev tools fail
Best Practices ¶
When to enable dev tools:
- Development mode only (check environment variable)
- Debug sessions (enable via F12 or command-line flag)
- Performance profiling (measure overhead separately)
- CI/CD debugging (export logs for analysis)
When to disable:
- Production builds (compile-time or runtime check)
- Performance benchmarks (overhead skews results)
- When memory is constrained
Export best practices:
- Always use sanitization for shared exports
- Use compression for network transfer
- Use incremental exports for long sessions
- Use streaming for very large datasets
Examples ¶
See cmd/examples/09-devtools/ for comprehensive examples:
- 01-basic-enablement: Zero-config getting started
- 02-component-inspection: Component tree navigation
- 03-state-debugging: Ref and Computed tracking
- 04-event-monitoring: Event emission and filtering
- 05-performance-profiling: Render performance analysis
- 06-reactive-cascade: Full reactive cascade visualization
- 07-export-import: Compression and format selection
- 08-custom-sanitization: PII removal and patterns
- 09-custom-hooks: Implementing framework hooks
- 10-production-ready: Best practices guide
See Also ¶
- devtools.Enable: Main entry point
- devtools.Export: Debug session export
- devtools.Import: Debug session import
- devtools.NewSanitizer: Create sanitizer with templates
- devtools.RegisterHook: Integrate custom hooks
- FrameworkHook: Hook interface definition (in pkg/bubbly)
Version ¶
This documentation is for BubblyUI DevTools v1.0.
For updates and migration guides, see CHANGELOG.md.
Index ¶
- func DefaultPatterns() []string
- func DetectFormat(filename string) (string, error)
- func Disable()
- func GetSupportedFormats() []string
- func GetTemplateNames() []string
- func HandleUpdate(msg tea.Msg) tea.Cmd
- func IsEnabled() bool
- func NotifyComponentCreated(snapshot *ComponentSnapshot)
- func NotifyComponentMounted(id string)
- func NotifyComponentUnmounted(id string)
- func NotifyComponentUpdated(id string)
- func NotifyEvent(event *EventRecord)
- func NotifyRefChanged(refID string, oldValue, newValue interface{})
- func NotifyRenderComplete(componentID string, duration time.Duration)
- func RegisterFormat(format ExportFormat) error
- func RegisterMigration(mig VersionMigration) error
- func RegisterTemplate(name string, patterns []SanitizePattern) error
- func RenderView(appView string) string
- func SetCollector(collector *DataCollector)
- func Toggle()
- func ValidateMigrationChain() error
- type CommandRecord
- type CommandTimeline
- func (ct *CommandTimeline) Append(record CommandRecord)
- func (ct *CommandTimeline) Clear()
- func (ct *CommandTimeline) GetAll() []CommandRecord
- func (ct *CommandTimeline) GetCommandCount() int
- func (ct *CommandTimeline) GetCommands() []CommandRecord
- func (ct *CommandTimeline) GetMaxID() int64
- func (ct *CommandTimeline) IsPaused() bool
- func (ct *CommandTimeline) Pause()
- func (ct *CommandTimeline) RecordCommand(record CommandRecord)
- func (ct *CommandTimeline) Render(width int) string
- func (ct *CommandTimeline) Resume()
- type ComponentFilter
- func (cf *ComponentFilter) Apply(components []*ComponentSnapshot) []*ComponentSnapshot
- func (cf *ComponentFilter) WithCustom(custom FilterFunc) *ComponentFilter
- func (cf *ComponentFilter) WithStatuses(statuses []string) *ComponentFilter
- func (cf *ComponentFilter) WithTypes(types []string) *ComponentFilter
- type ComponentHook
- type ComponentInspector
- type ComponentInterface
- type ComponentPerformance
- type ComponentSnapshot
- type Config
- type DataCollector
- func (dc *DataCollector) AddComponentHook(hook ComponentHook)
- func (dc *DataCollector) AddEventHook(hook EventHook)
- func (dc *DataCollector) AddPerformanceHook(hook PerformanceHook)
- func (dc *DataCollector) AddStateHook(hook StateHook)
- func (dc *DataCollector) FireComponentCreated(snapshot *ComponentSnapshot)
- func (dc *DataCollector) FireComponentMounted(id string)
- func (dc *DataCollector) FireComponentUnmounted(id string)
- func (dc *DataCollector) FireComponentUpdated(id string)
- func (dc *DataCollector) FireEvent(event *EventRecord)
- func (dc *DataCollector) FireRefChanged(refID string, oldValue, newValue interface{})
- func (dc *DataCollector) FireRenderComplete(componentID string, duration time.Duration)
- func (dc *DataCollector) RemoveComponentHook(hook ComponentHook)
- func (dc *DataCollector) RemoveEventHook(hook EventHook)
- func (dc *DataCollector) RemovePerformanceHook(hook PerformanceHook)
- func (dc *DataCollector) RemoveStateHook(hook StateHook)
- type DetailPanel
- func (dp *DetailPanel) GetActiveTab() int
- func (dp *DetailPanel) GetComponent() *ComponentSnapshot
- func (dp *DetailPanel) NextTab()
- func (dp *DetailPanel) PreviousTab()
- func (dp *DetailPanel) Render() string
- func (dp *DetailPanel) SetComponent(component *ComponentSnapshot)
- func (dp *DetailPanel) SwitchTab(index int)
- type DevTools
- func (dt *DevTools) Export(filename string, opts ExportOptions) error
- func (dt *DevTools) ExportFormat(filename, formatName string, opts ExportOptions) error
- func (dt *DevTools) ExportFull(filename string, opts ExportOptions) (*ExportCheckpoint, error)
- func (dt *DevTools) ExportIncremental(filename string, since *ExportCheckpoint) (*ExportCheckpoint, error)
- func (dt *DevTools) ExportStream(filename string, opts ExportOptions) error
- func (dt *DevTools) GetMCPServer() interface{}
- func (dt *DevTools) GetStore() *Store
- func (dt *DevTools) Import(filename string) error
- func (dt *DevTools) ImportDelta(filename string) error
- func (dt *DevTools) ImportFormat(filename, formatName string) error
- func (dt *DevTools) ImportFromReader(reader io.Reader) error
- func (dt *DevTools) IsEnabled() bool
- func (dt *DevTools) IsVisible() bool
- func (dt *DevTools) MCPEnabled() bool
- func (dt *DevTools) RenderWithApp(appView string) string
- func (dt *DevTools) SetMCPServer(server interface{})
- func (dt *DevTools) SetVisible(visible bool)
- func (dt *DevTools) ToggleVisibility()
- func (dt *DevTools) ValidateImport(data *ExportData) error
- type DryRunResult
- type EventFilter
- func (ef *EventFilter) Apply(events []EventRecord) []EventRecord
- func (ef *EventFilter) Clear()
- func (ef *EventFilter) GetNames() []string
- func (ef *EventFilter) GetSources() []string
- func (ef *EventFilter) GetTimeRange() *TimeRange
- func (ef *EventFilter) Matches(event EventRecord) bool
- func (ef *EventFilter) WithNames(names ...string) *EventFilter
- func (ef *EventFilter) WithSources(sources ...string) *EventFilter
- func (ef *EventFilter) WithTimeRange(start, end time.Time) *EventFilter
- type EventHook
- type EventLog
- type EventRecord
- type EventReplayer
- func (er *EventReplayer) GetProgress() (current int, total int)
- func (er *EventReplayer) GetSpeed() float64
- func (er *EventReplayer) IsPaused() bool
- func (er *EventReplayer) IsReplaying() bool
- func (er *EventReplayer) Pause()
- func (er *EventReplayer) Replay() tea.Cmd
- func (er *EventReplayer) Reset()
- func (er *EventReplayer) Resume() tea.Cmd
- func (er *EventReplayer) SetSpeed(speed float64) error
- type EventStatistics
- type EventTracker
- func (et *EventTracker) CaptureEvent(event EventRecord)
- func (et *EventTracker) Clear()
- func (et *EventTracker) GetEventCount() int
- func (et *EventTracker) GetFilter() string
- func (et *EventTracker) GetRecent(n int) []EventRecord
- func (et *EventTracker) GetStatistics() EventStatistics
- func (et *EventTracker) IsPaused() bool
- func (et *EventTracker) Pause()
- func (et *EventTracker) Render() string
- func (et *EventTracker) Resume()
- func (et *EventTracker) SetFilter(filter string)
- type ExportCheckpoint
- type ExportData
- type ExportFormat
- type ExportOptions
- type FilterFunc
- type FlameGraphRenderer
- type FlameNode
- type FocusTarget
- type FormatRegistry
- type GuardExecution
- type GuardResult
- type IncrementalExportData
- type Instrumentor
- type JSONFormat
- type KeyHandler
- type KeyboardHandler
- func (kh *KeyboardHandler) GetFocus() FocusTarget
- func (kh *KeyboardHandler) Handle(msg tea.KeyMsg) tea.Cmd
- func (kh *KeyboardHandler) Register(key string, handler KeyHandler)
- func (kh *KeyboardHandler) RegisterGlobal(key string, handler KeyHandler)
- func (kh *KeyboardHandler) RegisterWithFocus(key string, focus FocusTarget, handler KeyHandler)
- func (kh *KeyboardHandler) SetFocus(focus FocusTarget)
- func (kh *KeyboardHandler) Unregister(key string)
- type LayoutManager
- func (lm *LayoutManager) GetMode() LayoutMode
- func (lm *LayoutManager) GetRatio() float64
- func (lm *LayoutManager) GetSize() (width, height int)
- func (lm *LayoutManager) Render(appContent, toolsContent string) string
- func (lm *LayoutManager) SetMode(mode LayoutMode)
- func (lm *LayoutManager) SetRatio(ratio float64)
- func (lm *LayoutManager) SetSize(width, height int)
- type LayoutMode
- type MatchLocation
- type MessagePackFormat
- type PerformanceData
- type PerformanceHook
- type PerformanceMonitor
- func (pm *PerformanceMonitor) GetSortBy() SortBy
- func (pm *PerformanceMonitor) GetSortedComponents(sortBy SortBy) []*ComponentPerformance
- func (pm *PerformanceMonitor) RecordRender(componentID, componentName string, duration time.Duration)
- func (pm *PerformanceMonitor) Render(sortBy SortBy) string
- func (pm *PerformanceMonitor) SetSortBy(sortBy SortBy)
- type RefInterface
- type RefSnapshot
- type ReplayCommandMsg
- type ReplayCompletedMsg
- type ReplayEventMsg
- type ReplayPausedMsg
- type RouteRecord
- type RouterDebugger
- func (rd *RouterDebugger) Clear()
- func (rd *RouterDebugger) GetCurrentRoute() *router.Route
- func (rd *RouterDebugger) GetGuards() []GuardExecution
- func (rd *RouterDebugger) GetHistory() []RouteRecord
- func (rd *RouterDebugger) GetHistoryCount() int
- func (rd *RouterDebugger) RecordGuard(guardName string, result GuardResult, duration time.Duration)
- func (rd *RouterDebugger) RecordNavigation(from, to *router.Route, duration time.Duration, success bool)
- func (rd *RouterDebugger) Render() string
- type SanitizationStats
- type SanitizeOptions
- type SanitizePattern
- type Sanitizer
- func (s *Sanitizer) AddPattern(pattern, replacement string)
- func (s *Sanitizer) AddPatternWithPriority(pattern, replacement string, priority int, name string) error
- func (s *Sanitizer) GetLastStats() *SanitizationStats
- func (s *Sanitizer) GetPatterns() []SanitizePattern
- func (s *Sanitizer) LoadTemplate(name string) error
- func (s *Sanitizer) LoadTemplates(names ...string) error
- func (s *Sanitizer) MergeTemplates(names ...string) ([]SanitizePattern, error)
- func (s *Sanitizer) PatternCount() int
- func (s *Sanitizer) Preview(data *ExportData) *DryRunResult
- func (s *Sanitizer) ResetStats()
- func (s *Sanitizer) Sanitize(data *ExportData) *ExportData
- func (s *Sanitizer) SanitizeString(str string) string
- func (s *Sanitizer) SanitizeValue(val interface{}) interface{}
- func (s *Sanitizer) SanitizeValueOptimized(val interface{}) interface{}
- func (s *Sanitizer) SanitizeWithOptions(data *ExportData, opts SanitizeOptions) (*ExportData, *DryRunResult)
- type SearchWidget
- func (sw *SearchWidget) Clear()
- func (sw *SearchWidget) GetCursor() int
- func (sw *SearchWidget) GetQuery() string
- func (sw *SearchWidget) GetResultCount() int
- func (sw *SearchWidget) GetResults() []*ComponentSnapshot
- func (sw *SearchWidget) GetSelected() *ComponentSnapshot
- func (sw *SearchWidget) NextResult()
- func (sw *SearchWidget) PrevResult()
- func (sw *SearchWidget) Render() string
- func (sw *SearchWidget) Search(query string)
- func (sw *SearchWidget) SetComponents(components []*ComponentSnapshot)
- type SortBy
- type StateChange
- type StateHistory
- type StateHook
- type StateViewer
- func (sv *StateViewer) ClearSelection()
- func (sv *StateViewer) EditValue(id string, value interface{}) error
- func (sv *StateViewer) GetFilter() string
- func (sv *StateViewer) GetSelected() *RefSnapshot
- func (sv *StateViewer) Render() string
- func (sv *StateViewer) SelectRef(id string) bool
- func (sv *StateViewer) SetFilter(filter string)
- type Store
- func (s *Store) AddComponent(snapshot *ComponentSnapshot)
- func (s *Store) AddComponentChild(parentID, childID string)
- func (s *Store) Clear()
- func (s *Store) GetAllComponents() []*ComponentSnapshot
- func (s *Store) GetComponent(id string) *ComponentSnapshot
- func (s *Store) GetComponentChildren(componentID string) []string
- func (s *Store) GetEventLog() *EventLog
- func (s *Store) GetPerformanceData() *PerformanceData
- func (s *Store) GetRootComponents() []*ComponentSnapshot
- func (s *Store) GetSince(checkpoint *ExportCheckpoint) (*IncrementalExportData, error)
- func (s *Store) GetStateHistory() *StateHistory
- func (s *Store) RegisterRefOwner(componentID, refID string)
- func (s *Store) RemoveComponent(id string)
- func (s *Store) RemoveComponentChild(parentID, childID string)
- func (s *Store) UpdateRefValue(refID string, newValue interface{}) (string, bool)
- type StreamSanitizer
- type Tab
- type TabController
- type TabItem
- type TemplateRegistry
- type TimeRange
- type TimelineControls
- func (tc *TimelineControls) GetPosition() int
- func (tc *TimelineControls) GetSpeed() float64
- func (tc *TimelineControls) IsPaused() bool
- func (tc *TimelineControls) IsReplaying() bool
- func (tc *TimelineControls) Pause()
- func (tc *TimelineControls) Render(width int) string
- func (tc *TimelineControls) Replay() tea.Cmd
- func (tc *TimelineControls) Resume()
- func (tc *TimelineControls) Scrub(position int)
- func (tc *TimelineControls) ScrubBackward()
- func (tc *TimelineControls) ScrubForward()
- func (tc *TimelineControls) SetSpeed(speed float64) error
- type TreeView
- func (tv *TreeView) Collapse(id string)
- func (tv *TreeView) Expand(id string)
- func (tv *TreeView) GetExpandedIDs() map[string]bool
- func (tv *TreeView) GetRoot() *ComponentSnapshot
- func (tv *TreeView) GetSelected() *ComponentSnapshot
- func (tv *TreeView) IsExpanded(id string) bool
- func (tv *TreeView) Render() string
- func (tv *TreeView) Select(id string)
- func (tv *TreeView) SelectNext()
- func (tv *TreeView) SelectPrevious()
- func (tv *TreeView) SetExpandedIDs(expanded map[string]bool)
- func (tv *TreeView) Toggle(id string)
- type UI
- func (ui *UI) EnableAutoLayout()
- func (ui *UI) GetActivePanel() int
- func (ui *UI) GetLayoutMode() LayoutMode
- func (ui *UI) GetLayoutRatio() float64
- func (ui *UI) Init() tea.Cmd
- func (ui *UI) IsFocusMode() bool
- func (ui *UI) SetActivePanel(index int)
- func (ui *UI) SetAppContent(content string)
- func (ui *UI) SetFocusMode(enabled bool)
- func (ui *UI) SetLayoutMode(mode LayoutMode)
- func (ui *UI) SetLayoutRatio(ratio float64)
- func (ui *UI) SetManualLayoutMode(mode LayoutMode)
- func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd)
- func (ui *UI) View() string
- type VersionMigration
- type YAMLFormat
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultPatterns ¶
func DefaultPatterns() []string
DefaultPatterns returns the default sanitization patterns.
This is useful for understanding what patterns are applied by default or for creating a custom sanitizer with modified default patterns.
Returns:
- []string: List of default regex pattern strings
func DetectFormat ¶
DetectFormat detects the format from a filename.
Detection is performed by checking the file extension first. If the extension is not recognized, the function attempts to detect the format by reading the file content (not implemented yet).
Thread Safety:
Safe for concurrent use.
Example:
format, err := DetectFormat("debug.yaml")
// Returns: "yaml", nil
format, err := DetectFormat("debug.json.gz")
// Returns: "json", nil (strips .gz)
Parameters:
- filename: Path to the file
Returns:
- string: Detected format name
- error: nil on success, error if format cannot be detected
func Disable ¶
func Disable()
Disable disables the dev tools system.
This stops data collection and hides the UI. The singleton instance is preserved and can be re-enabled with Enable().
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
// Disable dev tools
devtools.Disable()
// Verify disabled
if !devtools.IsEnabled() {
fmt.Println("Dev tools disabled")
}
func GetSupportedFormats ¶
func GetSupportedFormats() []string
GetSupportedFormats returns a list of all supported format names.
Thread Safety:
Safe for concurrent use.
Example:
formats := GetSupportedFormats() // Returns: ["json", "yaml", "msgpack"]
Returns:
- []string: Sorted list of format names
func GetTemplateNames ¶
func GetTemplateNames() []string
GetTemplateNames returns a sorted list of all available template names.
This includes both built-in templates (pii, pci, hipaa, gdpr) and any custom templates registered via RegisterTemplate().
Thread Safety:
Safe to call concurrently. Uses read lock on registry.
Example:
names := devtools.GetTemplateNames()
fmt.Printf("Available templates: %v\n", names)
// Output: Available templates: [gdpr hipaa pci pii]
Returns:
- []string: Sorted list of template names
func HandleUpdate ¶
HandleUpdate is a package-level function to handle Bubbletea messages for dev tools.
This is called automatically by bubbly.Wrap() to enable dev tools UI interaction. If dev tools are not enabled or not visible, it returns nil.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
// In wrapper.Update() cmd := devtools.HandleUpdate(msg)
Parameters:
- msg: The Bubbletea message
Returns:
- tea.Cmd: Command from dev tools UI, or nil
func IsEnabled ¶
func IsEnabled() bool
IsEnabled returns whether dev tools are currently enabled.
This is a package-level function that checks the global singleton state. Returns false if dev tools have never been enabled.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
if devtools.IsEnabled() {
// Dev tools are active
dt := devtools.Enable()
dt.SetVisible(true)
}
Returns:
- bool: true if dev tools are enabled, false otherwise
func NotifyComponentCreated ¶
func NotifyComponentCreated(snapshot *ComponentSnapshot)
NotifyComponentCreated notifies the collector that a component was created.
This should be called when a new component instance is created, typically in the component builder's Build() method.
If no collector is set, this is a no-op with minimal overhead.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
snapshot := &ComponentSnapshot{
ID: component.ID(),
Name: component.Name(),
}
devtools.NotifyComponentCreated(snapshot)
Parameters:
- snapshot: The component snapshot containing component state
func NotifyComponentMounted ¶
func NotifyComponentMounted(id string)
NotifyComponentMounted notifies the collector that a component was mounted.
This should be called when a component's Init() method completes and the component is ready for use.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
devtools.NotifyComponentMounted(component.ID())
Parameters:
- id: The component ID
func NotifyComponentUnmounted ¶
func NotifyComponentUnmounted(id string)
NotifyComponentUnmounted notifies the collector that a component was unmounted.
This should be called when a component is being destroyed and cleaned up.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
devtools.NotifyComponentUnmounted(component.ID())
Parameters:
- id: The component ID
func NotifyComponentUpdated ¶
func NotifyComponentUpdated(id string)
NotifyComponentUpdated notifies the collector that a component was updated.
This should be called when a component's Update() method is called with a Bubbletea message.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
devtools.NotifyComponentUpdated(component.ID())
Parameters:
- id: The component ID
func NotifyEvent ¶
func NotifyEvent(event *EventRecord)
NotifyEvent notifies the collector that an event was emitted.
This should be called when a component emits an event via Emit().
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
event := &EventRecord{
ID: generateID(),
Name: eventName,
SourceID: component.ID(),
Timestamp: time.Now(),
}
devtools.NotifyEvent(event)
Parameters:
- event: The event record
func NotifyRefChanged ¶
func NotifyRefChanged(refID string, oldValue, newValue interface{})
NotifyRefChanged notifies the collector that a ref's value changed.
This should be called in Ref.Set() after the value has been updated.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
devtools.NotifyRefChanged(ref.id, oldValue, newValue)
Parameters:
- refID: The ref ID
- oldValue: The previous value
- newValue: The new value
func NotifyRenderComplete ¶
NotifyRenderComplete notifies the collector that a component finished rendering.
This should be called after a component's View() method completes, with the duration of the render operation.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
start := time.Now() output := component.View() duration := time.Since(start) devtools.NotifyRenderComplete(component.ID(), duration)
Parameters:
- componentID: The component ID
- duration: How long the render took
func RegisterFormat ¶
func RegisterFormat(format ExportFormat) error
RegisterFormat registers a format in the global registry.
This is a convenience function for registering custom formats. If a format with the same name already exists, it will be replaced.
Thread Safety:
Safe for concurrent use.
Example:
RegisterFormat(&MyCustomFormat{})
Parameters:
- format: The ExportFormat implementation to register
Returns:
- error: Always returns nil (for future extensibility)
func RegisterMigration ¶
func RegisterMigration(mig VersionMigration) error
RegisterMigration registers a version migration.
Migrations are stored in a global registry and can be retrieved by GetMigrationPath(). Multiple migrations can be chained to migrate across multiple versions (e.g., 1.0 → 1.5 → 2.0).
Thread Safety:
Safe to call concurrently.
Example:
migration := &Migration_1_0_to_2_0{}
err := RegisterMigration(migration)
if err != nil {
log.Fatalf("Failed to register migration: %v", err)
}
func RegisterTemplate ¶
func RegisterTemplate(name string, patterns []SanitizePattern) error
RegisterTemplate registers a custom template in the global registry.
This allows applications to define their own compliance templates that can be loaded by name. Custom templates can be used alongside built-in templates.
Thread Safety:
Safe to call concurrently. Uses mutex to protect registry.
Example:
customPatterns := []devtools.SanitizePattern{
{
Pattern: regexp.MustCompile(`(?i)(internal[_-]?id)(["'\s:=]+)([A-Z0-9-]+)`),
Replacement: "${1}${2}[REDACTED_ID]",
Priority: 80,
Name: "internal_id",
},
}
err := devtools.RegisterTemplate("custom", customPatterns)
if err != nil {
log.Fatal(err)
}
Parameters:
- name: Template name (case-sensitive)
- patterns: Sanitization patterns for this template
Returns:
- error: Error if template name is empty or already exists
func RenderView ¶
RenderView is a package-level function to render the app view with dev tools.
This is called automatically by bubbly.Wrap() to integrate dev tools rendering. If dev tools are not enabled or not visible, it just returns the app view.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
// In wrapper.View() appView := m.component.View() return devtools.RenderView(appView)
Parameters:
- appView: The application's rendered view
Returns:
- string: Combined view of app + dev tools, or just app if dev tools disabled/hidden
func SetCollector ¶
func SetCollector(collector *DataCollector)
SetCollector sets the data collector for instrumentation.
When a collector is set, all subsequent instrumentation calls will forward to the collector's hooks. When set to nil, instrumentation is disabled.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
collector := devtools.NewDataCollector() devtools.SetCollector(collector)
Parameters:
- collector: The data collector to use, or nil to disable instrumentation
func Toggle ¶
func Toggle()
Toggle toggles the enabled state of dev tools.
If dev tools are disabled, this enables them. If enabled, this disables them. This is useful for keyboard shortcuts (e.g., F12 to toggle dev tools).
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
// Toggle dev tools (F12 handler)
func handleF12() {
devtools.Toggle()
}
func ValidateMigrationChain ¶
func ValidateMigrationChain() error
ValidateMigrationChain validates that all registered migrations form a valid chain.
Checks for: - Duplicate migrations (same from/to) - already handled by RegisterMigration - Gaps in migration chain (disconnected versions) - Circular dependencies
Thread Safety:
Safe to call concurrently.
Example:
if err := ValidateMigrationChain(); err != nil {
log.Fatalf("Invalid migration chain: %v", err)
}
Types ¶
type CommandRecord ¶
type CommandRecord struct {
// SeqID is the auto-incrementing sequence ID for incremental exports
SeqID int64 `json:"seq_id"`
// ID is the unique identifier for this command
ID string
// Type is the command type or name
Type string
// Source identifies what generated the command (component ID, function name, etc.)
Source string
// Generated is when the command was created
Generated time.Time
// Executed is when the command was executed
Executed time.Time
// Duration is how long the command took to execute
Duration time.Duration
}
CommandRecord represents a single command execution.
type CommandTimeline ¶
type CommandTimeline struct {
// contains filtered or unexported fields
}
CommandTimeline tracks Bubbletea command execution over time.
It maintains a circular buffer of command records with configurable maximum size and provides timeline visualization capabilities. Commands can be paused/resumed for analysis without losing historical data.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
timeline := NewCommandTimeline(1000)
timeline.RecordCommand(CommandRecord{
ID: "cmd-1",
Type: "fetchData",
Source: "DataLoader",
Generated: time.Now(),
Executed: time.Now().Add(5 * time.Millisecond),
Duration: 5 * time.Millisecond,
})
output := timeline.Render(80)
func NewCommandTimeline ¶
func NewCommandTimeline(maxSize int) *CommandTimeline
NewCommandTimeline creates a new command timeline with the specified maximum size.
The maxSize parameter determines how many command records to keep in memory. When the limit is reached, the oldest records are discarded.
Example:
timeline := NewCommandTimeline(1000) // Keep last 1000 commands
Parameters:
- maxSize: Maximum number of command records to keep
Returns:
- *CommandTimeline: A new command timeline instance
func (*CommandTimeline) Append ¶
func (ct *CommandTimeline) Append(record CommandRecord)
Append adds a command record to the timeline (alias for RecordCommand).
This method exists for consistency with EventLog and StateHistory APIs.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- record: The command record to add
func (*CommandTimeline) Clear ¶
func (ct *CommandTimeline) Clear()
Clear removes all commands from the timeline.
The paused state is not affected.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
timeline.Clear()
func (*CommandTimeline) GetAll ¶
func (ct *CommandTimeline) GetAll() []CommandRecord
GetAll returns a copy of all commands in the timeline (alias for GetCommands).
This method exists for consistency with EventLog and StateHistory APIs.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- []CommandRecord: A copy of all command records
func (*CommandTimeline) GetCommandCount ¶
func (ct *CommandTimeline) GetCommandCount() int
GetCommandCount returns the number of commands in the timeline.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- int: The number of commands currently stored
func (*CommandTimeline) GetCommands ¶
func (ct *CommandTimeline) GetCommands() []CommandRecord
GetCommands returns a copy of all commands in the timeline.
The returned slice is a copy and can be safely modified without affecting the internal state.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- []CommandRecord: A copy of all command records
func (*CommandTimeline) GetMaxID ¶
func (ct *CommandTimeline) GetMaxID() int64
GetMaxID returns the highest sequence ID assigned to any command.
This is used for creating checkpoints in incremental exports.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- int64: The highest sequence ID, or 0 if no commands exist
func (*CommandTimeline) IsPaused ¶
func (ct *CommandTimeline) IsPaused() bool
IsPaused returns whether the timeline is currently paused.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- bool: True if paused, false otherwise
func (*CommandTimeline) Pause ¶
func (ct *CommandTimeline) Pause()
Pause stops recording new commands.
Commands recorded while paused are ignored. Existing commands remain in the timeline.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
timeline.Pause() // Commands recorded here will be ignored timeline.Resume()
func (*CommandTimeline) RecordCommand ¶
func (ct *CommandTimeline) RecordCommand(record CommandRecord)
RecordCommand adds a command record to the timeline.
If the timeline is paused, the command is not recorded. If the timeline is at maximum capacity, the oldest record is removed to make room for the new one. An auto-incrementing sequence ID is assigned to the command for incremental export tracking.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
timeline.RecordCommand(CommandRecord{
ID: "cmd-1",
Type: "fetchData",
Source: "DataLoader",
Generated: time.Now(),
Executed: time.Now().Add(5 * time.Millisecond),
Duration: 5 * time.Millisecond,
})
Parameters:
- record: The command record to add
func (*CommandTimeline) Render ¶
func (ct *CommandTimeline) Render(width int) string
Render generates a visual timeline representation of command execution.
The timeline shows commands as horizontal bars with their duration and timing. The width parameter controls the maximum width of the visualization.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
output := timeline.Render(80) // 80 character width fmt.Println(output)
Parameters:
- width: Maximum width for the timeline visualization
Returns:
- string: The rendered timeline with Lipgloss styling
func (*CommandTimeline) Resume ¶
func (ct *CommandTimeline) Resume()
Resume resumes recording new commands.
After calling Resume, new commands will be recorded normally.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
timeline.Resume() // Commands recorded here will be captured
type ComponentFilter ¶
type ComponentFilter struct {
// contains filtered or unexported fields
}
ComponentFilter provides flexible filtering of component snapshots.
The filter supports three types of criteria: - Type filtering: Match components by their Type field - Status filtering: Match components by their Status field - Custom filtering: Match components using a custom predicate function
All filter criteria are combined with AND logic - a component must pass all active filters to be included in the results.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
filter := devtools.NewComponentFilter().
WithTypes([]string{"button", "input"}).
WithStatuses([]string{"mounted"}).
WithCustom(func(c *ComponentSnapshot) bool {
return len(c.Refs) > 0
})
filtered := filter.Apply(components)
func NewComponentFilter ¶
func NewComponentFilter() *ComponentFilter
NewComponentFilter creates a new component filter with no criteria.
An empty filter returns all components. Use the With* methods to add filter criteria.
func (*ComponentFilter) Apply ¶
func (cf *ComponentFilter) Apply(components []*ComponentSnapshot) []*ComponentSnapshot
Apply filters the provided components based on the configured criteria.
The method returns a new slice containing only the components that pass all active filters. The original slice is not modified.
Filter logic: - If no filters are configured, all components are returned - Type filter: component.Type must be in the types list - Status filter: component.Status must be in the statuses list - Custom filter: custom function must return true - All filters are combined with AND logic
Thread Safety:
This method is thread-safe and can be called concurrently.
func (*ComponentFilter) WithCustom ¶
func (cf *ComponentFilter) WithCustom(custom FilterFunc) *ComponentFilter
WithCustom adds a custom filter function.
The custom function is called for each component and should return true if the component should be included. A nil custom function disables custom filtering.
This method returns the filter for method chaining.
func (*ComponentFilter) WithStatuses ¶
func (cf *ComponentFilter) WithStatuses(statuses []string) *ComponentFilter
WithStatuses adds status filtering to the filter.
Components will be included if their Status field matches any of the provided statuses. An empty statuses slice disables status filtering.
This method returns the filter for method chaining.
func (*ComponentFilter) WithTypes ¶
func (cf *ComponentFilter) WithTypes(types []string) *ComponentFilter
WithTypes adds type filtering to the filter.
Components will be included if their Type field matches any of the provided types. An empty types slice disables type filtering.
This method returns the filter for method chaining.
type ComponentHook ¶
type ComponentHook interface {
// OnComponentCreated is called when a component is created
OnComponentCreated(snapshot *ComponentSnapshot)
// OnComponentMounted is called when a component is mounted
OnComponentMounted(id string)
// OnComponentUpdated is called when a component is updated
OnComponentUpdated(id string)
// OnComponentUnmounted is called when a component is unmounted
OnComponentUnmounted(id string)
}
ComponentHook is called when component lifecycle events occur.
Implementations must be thread-safe as hooks can be called concurrently from multiple goroutines.
If a hook panics, the panic is recovered and reported via the observability system. The panic does not affect other hooks or the host application.
Example:
type MyHook struct{}
func (h *MyHook) OnComponentCreated(snapshot *ComponentSnapshot) {
fmt.Printf("Component created: %s\n", snapshot.Name)
}
func (h *MyHook) OnComponentMounted(id string) {
fmt.Printf("Component mounted: %s\n", id)
}
// ... implement other methods
type ComponentInspector ¶
type ComponentInspector struct {
// contains filtered or unexported fields
}
ComponentInspector provides a complete component inspection interface integrating tree view, detail panel, search, and filtering.
The inspector supports: - Hierarchical component tree navigation - Detailed component inspection with tabs - Search functionality for finding components - Filtering by type and status - Keyboard-driven navigation - Live updates as component tree changes
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
inspector := devtools.NewComponentInspector(rootSnapshot) // Handle Bubbletea messages cmd := inspector.Update(msg) // Render the inspector output := inspector.View()
func NewComponentInspector ¶
func NewComponentInspector(root *ComponentSnapshot) *ComponentInspector
NewComponentInspector creates a new component inspector with the given root component.
The inspector is initialized with: - TreeView for hierarchical display - DetailPanel for component details - SearchWidget for finding components - ComponentFilter for filtering
The root component can be nil, in which case the inspector will display an empty state until a root is set via SetRoot.
CRITICAL UX: Root is auto-selected and auto-expanded for better default experience.
func (*ComponentInspector) ApplyFilter ¶
func (ci *ComponentInspector) ApplyFilter()
ApplyFilter applies the current filter to the search results.
This method updates the search widget to show only components that pass the filter criteria.
func (*ComponentInspector) SetRoot ¶
func (ci *ComponentInspector) SetRoot(root *ComponentSnapshot)
SetRoot updates the root component and refreshes all sub-components.
This method should be called when the component tree changes to keep the inspector in sync with the application state.
CRITICAL UX BEHAVIOR: 1. Preserves expansion state (which nodes are expanded/collapsed) 2. If a component is currently selected, tries to preserve the selection 3. If selection cannot be preserved (component removed), selects root 4. If nothing was selected, auto-selects root 5. Auto-expands root on first load (better default UX)
func (*ComponentInspector) Update ¶
func (ci *ComponentInspector) Update(msg tea.Msg) tea.Cmd
Update handles Bubbletea messages and updates the inspector state.
Supported keyboard controls: - Up/Down: Navigate tree - Enter: Toggle node expansion - Tab: Next detail panel tab - Shift+Tab: Previous detail panel tab - Ctrl+F: Enter search mode - Esc: Exit search mode
Returns a tea.Cmd if any async operations are needed, nil otherwise.
func (*ComponentInspector) View ¶
func (ci *ComponentInspector) View() string
View renders the complete inspector interface.
The output includes: - Component tree view (left side) - Detail panel (right side) - Search widget (when in search mode)
Layout is responsive and uses Lipgloss for styling.
type ComponentInterface ¶
type ComponentInterface interface {
// GetName returns the component's name
GetName() string
// GetID returns the component's unique ID
GetID() string
// GetState returns the component's state map (refs, computed values)
GetState() map[string]interface{}
// GetProps returns the component's props
GetProps() interface{}
// GetParent returns the parent component (nil for root)
GetParent() ComponentInterface
// GetChildren returns child components
GetChildren() []ComponentInterface
}
ComponentInterface defines the interface that components must implement to be captured by the dev tools snapshot system.
This interface provides read-only access to component internals without exposing the full component implementation.
type ComponentPerformance ¶
type ComponentPerformance struct {
// ComponentID is the unique identifier of the component
ComponentID string
// ComponentName is the human-readable name
ComponentName string
// RenderCount is the total number of renders
RenderCount int64
// TotalRenderTime is the cumulative render time
TotalRenderTime time.Duration
// AvgRenderTime is the average render time
AvgRenderTime time.Duration
// MaxRenderTime is the slowest render
MaxRenderTime time.Duration
// MinRenderTime is the fastest render
MinRenderTime time.Duration
// MemoryUsage is the estimated memory usage in bytes
MemoryUsage uint64
// LastUpdate is when metrics were last updated
LastUpdate time.Time
}
ComponentPerformance holds performance metrics for a single component.
type ComponentSnapshot ¶
type ComponentSnapshot struct {
// ID is the unique identifier of the component instance
ID string
// Name is the human-readable name of the component
Name string
// Type is the Go type name of the component
Type string
// Status is the component's lifecycle status (e.g., "mounted", "unmounted", "updated")
Status string
// Parent is a reference to the parent component snapshot (nil for root)
Parent *ComponentSnapshot
// Children are snapshots of child components
Children []*ComponentSnapshot
// State contains the component's reactive state (refs, computed values)
State map[string]interface{}
// Props contains the component's properties
Props map[string]interface{}
// Refs are snapshots of reactive references in the component
Refs []*RefSnapshot
// Timestamp is when this snapshot was created
Timestamp time.Time
}
ComponentSnapshot captures the state of a component at a specific point in time.
This snapshot includes all relevant information about the component including its identity, hierarchy, state, props, and performance metrics. Snapshots are immutable and represent a frozen view of the component.
Thread Safety:
Snapshots are immutable after creation and safe to share across goroutines.
Example:
snapshot := &ComponentSnapshot{
ID: "component-123",
Name: "Counter",
Type: "bubbly.Component",
Timestamp: time.Now(),
}
func CaptureComponent ¶
func CaptureComponent(comp ComponentInterface) *ComponentSnapshot
CaptureComponent creates a snapshot of a component's current state.
This function captures all relevant information about the component including its identity, hierarchy, state, and props. The snapshot is immutable and represents a frozen view of the component at the time of capture.
Thread Safety:
This function is thread-safe and can be called concurrently. The returned snapshot is immutable and safe to share across goroutines.
Example:
snapshot := devtools.CaptureComponent(component)
fmt.Printf("Component: %s (ID: %s)\n", snapshot.Name, snapshot.ID)
fmt.Printf("State: %d refs\n", len(snapshot.Refs))
type Config ¶
type Config struct {
// Enabled controls whether dev tools are active
Enabled bool `json:"enabled"`
// LayoutMode determines how dev tools UI is displayed
LayoutMode LayoutMode `json:"layoutMode"`
// SplitRatio controls the split between app and dev tools (0.1-0.9)
// For horizontal: ratio of width for app (0.6 = 60% app, 40% tools)
// For vertical: ratio of height for app
SplitRatio float64 `json:"splitRatio"`
// MaxComponents limits the number of components to track
// Prevents memory issues with very large component trees
MaxComponents int `json:"maxComponents"`
// MaxEvents limits the number of events to keep in history
// Older events are discarded when limit is reached
MaxEvents int `json:"maxEvents"`
// MaxStateHistory limits the number of state changes to track
// Older state changes are discarded when limit is reached
MaxStateHistory int `json:"maxStateHistory"`
// SamplingRate controls what percentage of data to collect (0.0-1.0)
// 1.0 = collect everything, 0.5 = collect 50%, 0.0 = collect nothing
// Lower values reduce overhead but may miss events
SamplingRate float64 `json:"samplingRate"`
}
Config holds configuration options for the dev tools system.
Configuration can be loaded from JSON files, set programmatically, or overridden via environment variables. The configuration controls behavior like layout mode, data limits, and sampling rates.
Thread Safety:
Config instances are not thread-safe. Create separate instances for concurrent use or protect access with a mutex.
Example:
// Use defaults
cfg := devtools.DefaultConfig()
// Load from file
cfg, err := devtools.LoadConfig("devtools.json")
if err != nil {
log.Fatal(err)
}
// Apply environment overrides
cfg.ApplyEnvOverrides()
// Validate
if err := cfg.Validate(); err != nil {
log.Fatal(err)
}
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns a Config with sensible default values.
The defaults are optimized for typical development workflows:
- Dev tools enabled
- Horizontal layout (60/40 split)
- Track up to 10,000 components
- Keep last 5,000 events
- Keep last 1,000 state changes
- 100% sampling rate (collect all data)
These defaults can be overridden by loading a config file or setting environment variables.
Example:
cfg := devtools.DefaultConfig() cfg.MaxEvents = 10000 // Increase event history cfg.ApplyEnvOverrides() // Allow env vars to override
Returns:
- *Config: A new config with default values
func LoadConfig ¶
LoadConfig loads configuration from a JSON file.
The file should contain a JSON object with config fields. Missing fields will have zero values (not defaults). After loading, you should typically call Validate() to ensure the config is valid.
Thread Safety:
Safe to call concurrently (reads file, creates new Config).
Example:
cfg, err := devtools.LoadConfig("devtools.json")
if err != nil {
log.Fatal(err)
}
if err := cfg.Validate(); err != nil {
log.Fatal(err)
}
Parameters:
- path: Path to JSON config file
Returns:
- *Config: Loaded configuration
- error: File read error, JSON parse error, or validation error
func (*Config) ApplyEnvOverrides ¶
func (c *Config) ApplyEnvOverrides()
func (*Config) Validate ¶
Validate checks that the configuration values are valid.
This method verifies that:
- Split ratio is between 0.1 and 0.9
- Max components is positive
- Max events is positive
- Max state history is positive
- Sampling rate is between 0.0 and 1.0
- Layout mode is valid
Call this after loading config or modifying values to ensure validity.
Example:
cfg := devtools.DefaultConfig()
cfg.SplitRatio = 0.95 // Invalid
if err := cfg.Validate(); err != nil {
log.Printf("Invalid config: %v", err)
}
Returns:
- error: Validation error, or nil if config is valid
type DataCollector ¶
type DataCollector struct {
// contains filtered or unexported fields
}
DataCollector manages hooks for collecting data from the application.
The collector maintains lists of registered hooks and provides methods to fire events to all registered hooks. It ensures thread-safe access and protects the application from panicking hooks.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
collector := NewDataCollector()
// Register hooks
collector.AddComponentHook(&MyComponentHook{})
collector.AddStateHook(&MyStateHook{})
// Fire events
snapshot := &ComponentSnapshot{ID: "comp-1", Name: "Counter"}
collector.FireComponentCreated(snapshot)
func GetCollector ¶
func GetCollector() *DataCollector
GetCollector returns the current data collector.
Returns nil if instrumentation is disabled.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- *DataCollector: The current collector, or nil if disabled
func NewDataCollector ¶
func NewDataCollector() *DataCollector
NewDataCollector creates a new data collector with empty hook lists.
The returned collector is ready to use and thread-safe.
Example:
collector := NewDataCollector()
collector.AddComponentHook(&MyHook{})
Returns:
- *DataCollector: A new data collector instance
func (*DataCollector) AddComponentHook ¶
func (dc *DataCollector) AddComponentHook(hook ComponentHook)
AddComponentHook registers a component lifecycle hook.
The hook will be called for all future component lifecycle events. Hooks are called in the order they were registered.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
hook := &MyComponentHook{}
collector.AddComponentHook(hook)
Parameters:
- hook: The hook to register
func (*DataCollector) AddEventHook ¶
func (dc *DataCollector) AddEventHook(hook EventHook)
AddEventHook registers an event hook.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- hook: The hook to register
func (*DataCollector) AddPerformanceHook ¶
func (dc *DataCollector) AddPerformanceHook(hook PerformanceHook)
AddPerformanceHook registers a performance hook.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- hook: The hook to register
func (*DataCollector) AddStateHook ¶
func (dc *DataCollector) AddStateHook(hook StateHook)
AddStateHook registers a state change hook.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- hook: The hook to register
func (*DataCollector) FireComponentCreated ¶
func (dc *DataCollector) FireComponentCreated(snapshot *ComponentSnapshot)
FireComponentCreated calls OnComponentCreated on all registered component hooks.
If a hook panics, the panic is recovered and reported via the observability system. Other hooks continue to execute normally.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
snapshot := &ComponentSnapshot{
ID: "comp-1",
Name: "Counter",
}
collector.FireComponentCreated(snapshot)
Parameters:
- snapshot: The component snapshot
func (*DataCollector) FireComponentMounted ¶
func (dc *DataCollector) FireComponentMounted(id string)
FireComponentMounted calls OnComponentMounted on all registered component hooks.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- id: The component ID
func (*DataCollector) FireComponentUnmounted ¶
func (dc *DataCollector) FireComponentUnmounted(id string)
FireComponentUnmounted calls OnComponentUnmounted on all registered component hooks.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- id: The component ID
func (*DataCollector) FireComponentUpdated ¶
func (dc *DataCollector) FireComponentUpdated(id string)
FireComponentUpdated calls OnComponentUpdated on all registered component hooks.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- id: The component ID
func (*DataCollector) FireEvent ¶
func (dc *DataCollector) FireEvent(event *EventRecord)
FireEvent calls OnEvent on all registered event hooks.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- event: The event record
func (*DataCollector) FireRefChanged ¶
func (dc *DataCollector) FireRefChanged(refID string, oldValue, newValue interface{})
FireRefChanged calls OnRefChanged on all registered state hooks.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- refID: The ref ID
- oldValue: The previous value
- newValue: The new value
func (*DataCollector) FireRenderComplete ¶
func (dc *DataCollector) FireRenderComplete(componentID string, duration time.Duration)
FireRenderComplete calls OnRenderComplete on all registered performance hooks.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- componentID: The component ID
- duration: How long the render took
func (*DataCollector) RemoveComponentHook ¶
func (dc *DataCollector) RemoveComponentHook(hook ComponentHook)
RemoveComponentHook unregisters a component lifecycle hook.
The hook will no longer be called for future events. If the hook is not registered, this is a no-op.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
collector.RemoveComponentHook(hook)
Parameters:
- hook: The hook to unregister
func (*DataCollector) RemoveEventHook ¶
func (dc *DataCollector) RemoveEventHook(hook EventHook)
RemoveEventHook unregisters an event hook.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- hook: The hook to unregister
func (*DataCollector) RemovePerformanceHook ¶
func (dc *DataCollector) RemovePerformanceHook(hook PerformanceHook)
RemovePerformanceHook unregisters a performance hook.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- hook: The hook to unregister
func (*DataCollector) RemoveStateHook ¶
func (dc *DataCollector) RemoveStateHook(hook StateHook)
RemoveStateHook unregisters a state change hook.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- hook: The hook to unregister
type DetailPanel ¶
type DetailPanel struct {
// contains filtered or unexported fields
}
DetailPanel displays detailed information about a selected component with tabbed views for different aspects (State, Props, Events).
The detail panel supports: - Multiple tabs for organizing component information - Tab switching with keyboard navigation - Thread-safe concurrent access - Graceful handling of nil components
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
dp := devtools.NewDetailPanel(componentSnapshot) dp.SwitchTab(1) // Switch to Props tab output := dp.Render()
func NewDetailPanel ¶
func NewDetailPanel(component *ComponentSnapshot) *DetailPanel
NewDetailPanel creates a new detail panel for the given component.
The panel is initialized with three default tabs: - State: Shows reactive state (refs) - Props: Shows component properties - Events: Shows event history (placeholder)
The component can be nil, in which case the panel will display a "No component selected" message.
func (*DetailPanel) GetActiveTab ¶
func (dp *DetailPanel) GetActiveTab() int
GetActiveTab returns the index of the currently active tab.
func (*DetailPanel) GetComponent ¶
func (dp *DetailPanel) GetComponent() *ComponentSnapshot
GetComponent returns the currently displayed component.
func (*DetailPanel) NextTab ¶
func (dp *DetailPanel) NextTab()
NextTab switches to the next tab, wrapping around to the first tab if currently on the last tab.
func (*DetailPanel) PreviousTab ¶
func (dp *DetailPanel) PreviousTab()
PreviousTab switches to the previous tab, wrapping around to the last tab if currently on the first tab.
func (*DetailPanel) Render ¶
func (dp *DetailPanel) Render() string
Render generates the visual representation of the detail panel.
The output includes: - Component header with name and type - Tab navigation bar - Active tab content
Returns a styled message if no component is selected.
func (*DetailPanel) SetComponent ¶
func (dp *DetailPanel) SetComponent(component *ComponentSnapshot)
SetComponent updates the component being displayed.
The component can be nil, in which case the panel will display a "No component selected" message.
func (*DetailPanel) SwitchTab ¶
func (dp *DetailPanel) SwitchTab(index int)
SwitchTab switches to the tab at the given index.
If the index is out of bounds, the active tab remains unchanged.
type DevTools ¶
type DevTools struct {
// contains filtered or unexported fields
}
DevTools is the main dev tools instance that manages the entire debugging system.
It coordinates data collection, storage, and UI presentation for debugging BubblyUI applications. The instance is created as a singleton and accessed through package-level functions.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Lifecycle:
- Enable() - Creates and initializes the singleton
- SetVisible(true) - Shows the dev tools UI
- ... debugging work ...
- SetVisible(false) - Hides the UI (still collecting data)
- Disable() - Stops data collection and cleanup
Example:
dt := devtools.Enable() dt.SetVisible(true) defer dt.SetVisible(false)
func Enable ¶
func Enable() *DevTools
Enable creates and enables the dev tools singleton.
This function is idempotent - calling it multiple times returns the same instance. The dev tools are initialized on first call and subsequent calls just return the existing instance.
The returned DevTools instance is enabled and ready to use, but not visible by default. Call SetVisible(true) to show the UI.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
// Enable dev tools
dt := devtools.Enable()
// Show UI
dt.SetVisible(true)
// Check state
if dt.IsEnabled() {
fmt.Println("Dev tools active")
}
Returns:
- *DevTools: The singleton dev tools instance
func (*DevTools) Export ¶
func (dt *DevTools) Export(filename string, opts ExportOptions) error
Export writes dev tools debug data to a JSON file.
The export includes version information, timestamp, and optionally components, state history, events, and performance metrics based on the provided options. If sanitization is enabled, sensitive data matching the redact patterns will be replaced with "[REDACTED]".
Thread Safety:
Safe to call concurrently. Uses read lock on DevTools.
Example:
opts := ExportOptions{
IncludeComponents: true,
IncludeState: true,
IncludeEvents: true,
Sanitize: true,
RedactPatterns: []string{"password", "token"},
}
err := devtools.Export("debug-state.json", opts)
if err != nil {
log.Printf("Export failed: %v", err)
}
Parameters:
- filename: Path to the output JSON file
- opts: Export options controlling what data to include
Returns:
- error: nil on success, error describing the failure otherwise
func (*DevTools) ExportFormat ¶
func (dt *DevTools) ExportFormat(filename, formatName string, opts ExportOptions) error
ExportFormat writes dev tools debug data to a file using the specified format.
This method supports multiple export formats (JSON, YAML, MessagePack) and automatically handles compression if the filename ends with .gz.
Thread Safety:
Safe to call concurrently. Uses read lock on DevTools.
Example:
// Export as YAML
err := devtools.ExportFormat("debug.yaml", "yaml", ExportOptions{
IncludeComponents: true,
IncludeState: true,
})
// Export as MessagePack with compression
err := devtools.ExportFormat("debug.msgpack.gz", "msgpack", ExportOptions{
IncludeEvents: true,
Compress: true,
})
Parameters:
- filename: Path to the output file
- formatName: Format name ("json", "yaml", "msgpack")
- opts: Export options controlling what data to include
Returns:
- error: nil on success, error describing the failure otherwise
func (*DevTools) ExportFull ¶
func (dt *DevTools) ExportFull(filename string, opts ExportOptions) (*ExportCheckpoint, error)
ExportFull writes a complete export and returns a checkpoint for future incremental exports.
This method exports all current data and creates a checkpoint marking the highest IDs exported. The checkpoint can be used with ExportIncremental() to export only changes since this point.
Thread Safety:
Safe to call concurrently. Uses read lock on DevTools.
Example:
opts := ExportOptions{
IncludeComponents: true,
IncludeState: true,
IncludeEvents: true,
}
checkpoint, err := devtools.ExportFull("full-export.json", opts)
if err != nil {
log.Printf("Export failed: %v", err)
}
// Save checkpoint for later incremental exports
saveCheckpoint(checkpoint)
Parameters:
- filename: Path to the output JSON file
- opts: Export options controlling what data to include
Returns:
- *ExportCheckpoint: Checkpoint for future incremental exports
- error: nil on success, error describing the failure otherwise
func (*DevTools) ExportIncremental ¶
func (dt *DevTools) ExportIncremental(filename string, since *ExportCheckpoint) (*ExportCheckpoint, error)
ExportIncremental writes only changes since the last checkpoint.
This method exports only data created after the provided checkpoint, resulting in much smaller file sizes for long-running sessions. The returned checkpoint can be used for the next incremental export.
Thread Safety:
Safe to call concurrently. Uses read lock on DevTools.
Example:
// After initial full export
checkpoint, _ := devtools.ExportFull("full.json", opts)
// Later, export only changes
newCheckpoint, err := devtools.ExportIncremental("delta1.json", checkpoint)
if err != nil {
log.Printf("Incremental export failed: %v", err)
}
// Chain incrementals
checkpoint2, _ := devtools.ExportIncremental("delta2.json", newCheckpoint)
Parameters:
- filename: Path to the output JSON file
- since: Checkpoint from previous export
Returns:
- *ExportCheckpoint: New checkpoint for future incremental exports
- error: nil on success, error describing the failure otherwise
func (*DevTools) ExportStream ¶
func (dt *DevTools) ExportStream(filename string, opts ExportOptions) error
ExportStream writes dev tools debug data to a file using streaming mode.
This method is designed for large exports (>100MB) where loading the entire dataset into memory would cause out-of-memory errors. It processes data incrementally with bounded memory usage (O(buffer size)).
The export uses json.Encoder for streaming output and bufio.Writer for efficient buffered I/O. Progress callbacks are invoked periodically to report bytes processed.
Memory Guarantees:
- Memory usage stays under 100MB regardless of export size
- Processes data component-by-component
- Suitable for exports >100MB
Performance:
- Target: <10% slower than in-memory Export()
- Constant memory usage
- Efficient for large datasets
Thread Safety:
Safe to call concurrently. Uses read lock on DevTools.
Example:
opts := ExportOptions{
IncludeComponents: true,
IncludeState: true,
Sanitize: true,
UseStreaming: true,
ProgressCallback: func(bytes int64) {
fmt.Printf("Processed: %d bytes\n", bytes)
},
}
err := devtools.ExportStream("large-debug-state.json", opts)
if err != nil {
log.Printf("Export failed: %v", err)
}
Parameters:
- filename: Path to the output JSON file
- opts: Export options controlling what data to include
Returns:
- error: nil on success, error describing the failure otherwise
func (*DevTools) GetMCPServer ¶
func (dt *DevTools) GetMCPServer() interface{}
GetMCPServer returns the MCP server instance if MCP is enabled.
Returns nil if MCP was not enabled via mcp.EnableWithMCP(). The returned interface{} should be type-asserted to *mcp.Server.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable()
if server := dt.GetMCPServer(); server != nil {
mcpServer := server.(*mcp.Server)
fmt.Println("MCP enabled and running")
}
Returns:
- interface{}: The MCP server instance, or nil if not enabled
func (*DevTools) GetStore ¶
GetStore returns the Store instance.
This provides direct access to collected debug data. Used by the MCP server to expose component tree, state, events, and performance data to AI agents.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable()
store := dt.GetStore()
components := store.GetAllComponents()
fmt.Printf("Tracking %d components\n", len(components))
Returns:
- *Store: The Store instance
func (*DevTools) Import ¶
Import loads debug data from a JSON file and restores it to the dev tools store.
This function reads the specified file, validates the data, and replaces all existing data in the store with the imported data. Any existing components, state history, events, and performance metrics will be cleared.
The function automatically detects gzip compression by checking for gzip magic bytes (0x1f 0x8b) at the start of the file. If detected, the file is automatically decompressed before parsing.
Thread Safety:
Safe to call concurrently. Uses write lock on DevTools.
Example:
dt := devtools.Enable()
err := dt.Import("debug-state.json") // Works with .json or .json.gz
if err != nil {
log.Printf("Import failed: %v", err)
}
Parameters:
- filename: Path to the JSON file to import (compressed or uncompressed)
Returns:
- error: nil on success, error describing the failure otherwise
func (*DevTools) ImportDelta ¶
ImportDelta loads incremental data and merges it with existing data.
Unlike Import() which replaces all data, ImportDelta() appends the incremental data to the existing store. This allows reconstructing state from a full export plus multiple delta files.
Thread Safety:
Safe to call concurrently. Uses write lock on DevTools.
Example:
// Import full export first
dt.Import("full.json")
// Then import deltas in order
dt.ImportDelta("delta1.json")
dt.ImportDelta("delta2.json")
// State is now reconstructed from full + deltas
Parameters:
- filename: Path to the incremental JSON file
Returns:
- error: nil on success, error describing the failure otherwise
func (*DevTools) ImportFormat ¶
ImportFormat loads debug data from a file using the specified format.
This method supports multiple import formats (JSON, YAML, MessagePack) and automatically detects and handles gzip compression.
Thread Safety:
Safe to call concurrently. Uses write lock on DevTools.
Example:
// Import from YAML
err := devtools.ImportFormat("debug.yaml", "yaml")
// Import from compressed MessagePack
err := devtools.ImportFormat("debug.msgpack.gz", "msgpack")
// Auto-detect format from filename
format, _ := DetectFormat("debug.yaml")
err := devtools.ImportFormat("debug.yaml", format)
Parameters:
- filename: Path to the file to import
- formatName: Format name ("json", "yaml", "msgpack")
Returns:
- error: nil on success, error describing the failure otherwise
func (*DevTools) IsEnabled ¶
IsEnabled returns whether this DevTools instance is enabled.
This is a method on the DevTools instance. Use the package-level IsEnabled() function to check global state without getting the instance.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable()
if dt.IsEnabled() {
fmt.Println("This instance is enabled")
}
Returns:
- bool: true if this instance is enabled, false otherwise
func (*DevTools) IsVisible ¶
IsVisible returns whether the dev tools UI is currently visible.
The UI can be hidden while dev tools are still enabled and collecting data.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable()
if dt.IsVisible() {
fmt.Println("UI is shown")
} else {
fmt.Println("UI is hidden")
}
Returns:
- bool: true if UI is visible, false otherwise
func (*DevTools) MCPEnabled ¶
MCPEnabled returns true if MCP server is enabled.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable()
if dt.MCPEnabled() {
fmt.Println("MCP server is running")
} else {
fmt.Println("MCP server is not enabled")
}
Returns:
- bool: true if MCP server is enabled, false otherwise
func (*DevTools) RenderWithApp ¶
RenderWithApp renders the application view with dev tools UI if visible.
This is the main rendering method that should be called by the wrapper to combine the application view with the dev tools UI. If dev tools are not visible, it just returns the app view unchanged.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable() appView := component.View() finalView := dt.RenderWithApp(appView) // finalView contains app + dev tools if visible, or just app if hidden
Parameters:
- appView: The application's rendered view
Returns:
- string: Combined view of app + dev tools, or just app if dev tools hidden
func (*DevTools) SetMCPServer ¶
func (dt *DevTools) SetMCPServer(server interface{})
SetMCPServer sets the MCP server instance.
This is an internal method used by the mcp package to register the MCP server with DevTools. Application code should use mcp.EnableWithMCP() instead.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- server: The MCP server instance (should be *mcp.Server)
func (*DevTools) SetVisible ¶
SetVisible sets the visibility of the dev tools UI.
Setting visible to true shows the dev tools panel (split-pane or overlay). Setting visible to false hides the UI but continues data collection if enabled.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable() // Show UI dt.SetVisible(true) // Hide UI (still collecting data) dt.SetVisible(false)
Parameters:
- visible: true to show UI, false to hide UI
func (*DevTools) ToggleVisibility ¶
func (dt *DevTools) ToggleVisibility()
ToggleVisibility toggles the visibility of the dev tools UI.
If the UI is hidden, this shows it. If shown, this hides it. This is useful for keyboard shortcuts (e.g., F12 to toggle visibility).
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
dt := devtools.Enable()
// Toggle visibility (F12 handler)
func handleF12() {
dt.ToggleVisibility()
}
func (*DevTools) ValidateImport ¶
func (dt *DevTools) ValidateImport(data *ExportData) error
type DryRunResult ¶
type DryRunResult struct {
// Matches is the list of all locations where patterns matched
Matches []MatchLocation
// WouldRedactCount is the total number of values that would be redacted
WouldRedactCount int
// PreviewData is the original data structure (unchanged)
PreviewData interface{}
}
DryRunResult contains the results of a dry-run sanitization.
This structure is returned when sanitizing with DryRun: true in SanitizeOptions. It shows what would be redacted without actually modifying the data, allowing developers to validate patterns before applying them to production data.
Example:
sanitizer := devtools.NewSanitizer()
result := sanitizer.Preview(exportData)
fmt.Printf("Would redact %d values\n", result.WouldRedactCount)
for _, match := range result.Matches {
fmt.Printf(" %s: %s → %s\n", match.Path, match.Original, match.Redacted)
}
type EventFilter ¶
type EventFilter struct {
// contains filtered or unexported fields
}
EventFilter provides filtering capabilities for events.
It supports filtering by event names, source IDs, and time ranges. Multiple filter criteria can be combined, and all must match for an event to pass the filter.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
filter := NewEventFilter().
WithNames("click", "submit").
WithSources("button-1").
WithTimeRange(startTime, endTime)
if filter.Matches(event) {
// Event passes filter
}
filtered := filter.Apply(events)
func NewEventFilter ¶
func NewEventFilter() *EventFilter
NewEventFilter creates a new event filter with no criteria.
The filter starts empty and matches all events until criteria are added.
Example:
filter := NewEventFilter()
filter.WithNames("click", "submit")
Returns:
- *EventFilter: A new event filter instance
func (*EventFilter) Apply ¶
func (ef *EventFilter) Apply(events []EventRecord) []EventRecord
Apply filters a slice of events and returns only those that match.
The returned slice contains only events that pass all filter criteria. The original slice is not modified.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
filtered := filter.Apply(events)
fmt.Printf("Filtered %d events to %d\n", len(events), len(filtered))
Parameters:
- events: The events to filter
Returns:
- []EventRecord: Filtered events
func (*EventFilter) Clear ¶
func (ef *EventFilter) Clear()
Clear removes all filter criteria.
After clearing, the filter matches all events.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*EventFilter) GetNames ¶
func (ef *EventFilter) GetNames() []string
GetNames returns the current name filter criteria.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- []string: Copy of the name filter list
func (*EventFilter) GetSources ¶
func (ef *EventFilter) GetSources() []string
GetSources returns the current source filter criteria.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- []string: Copy of the source filter list
func (*EventFilter) GetTimeRange ¶
func (ef *EventFilter) GetTimeRange() *TimeRange
GetTimeRange returns the current time range filter criteria.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- *TimeRange: Copy of the time range, or nil if not set
func (*EventFilter) Matches ¶
func (ef *EventFilter) Matches(event EventRecord) bool
Matches checks if an event matches the filter criteria.
All filter criteria must match for the event to pass (AND logic). Within each criterion (names, sources), any match is sufficient (OR logic). If a criterion is empty, it matches all events.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
if filter.Matches(event) {
fmt.Println("Event passed filter")
}
Parameters:
- event: The event to check
Returns:
- bool: True if the event matches all criteria, false otherwise
func (*EventFilter) WithNames ¶
func (ef *EventFilter) WithNames(names ...string) *EventFilter
WithNames sets the event names to filter by.
If multiple names are provided, an event matches if its name matches any of the provided names (OR logic). Matching is case-insensitive and supports substring matching.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
filter.WithNames("click", "submit", "change")
Parameters:
- names: Event names to filter by
Returns:
- *EventFilter: The filter instance for method chaining
func (*EventFilter) WithSources ¶
func (ef *EventFilter) WithSources(sources ...string) *EventFilter
WithSources sets the source IDs to filter by.
If multiple sources are provided, an event matches if its source ID matches any of the provided sources (OR logic). Matching is case-insensitive and supports substring matching.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
filter.WithSources("button-1", "form-1")
Parameters:
- sources: Source IDs to filter by
Returns:
- *EventFilter: The filter instance for method chaining
func (*EventFilter) WithTimeRange ¶
func (ef *EventFilter) WithTimeRange(start, end time.Time) *EventFilter
WithTimeRange sets the time range to filter by.
Events are matched if their timestamp falls within the range (inclusive of both start and end).
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
start := time.Now().Add(-1 * time.Hour) end := time.Now() filter.WithTimeRange(start, end)
Parameters:
- start: Start time (inclusive)
- end: End time (inclusive)
Returns:
- *EventFilter: The filter instance for method chaining
type EventHook ¶
type EventHook interface {
// OnEvent is called when an event is emitted
OnEvent(event *EventRecord)
}
EventHook is called when events are emitted in the application.
Implementations must be thread-safe as hooks can be called concurrently from multiple goroutines.
Example:
type MyEventHook struct{}
func (h *MyEventHook) OnEvent(event *EventRecord) {
fmt.Printf("Event: %s from %s\n", event.Name, event.SourceID)
}
type EventLog ¶
type EventLog struct {
// contains filtered or unexported fields
}
EventLog maintains a log of events that occurred in the application.
It maintains a circular buffer of events with a configurable maximum size. When the buffer is full, the oldest events are discarded.
Thread Safety:
All methods are thread-safe and can be called concurrently.
func NewEventLog ¶
NewEventLog creates a new event log with the specified maximum size.
Parameters:
- maxSize: Maximum number of events to keep
Returns:
- *EventLog: A new event log instance
func (*EventLog) Append ¶
func (el *EventLog) Append(event EventRecord)
Append adds an event to the log.
If the log is at maximum capacity, the oldest event is removed. An auto-incrementing sequence ID is assigned to the event for incremental export tracking.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- event: The event to append
func (*EventLog) Clear ¶
func (el *EventLog) Clear()
Clear removes all events from the log.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*EventLog) GetMaxID ¶
GetMaxID returns the highest sequence ID assigned to any event.
This is used for creating checkpoints in incremental exports.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- int64: The highest sequence ID, or 0 if no events exist
func (*EventLog) GetRecent ¶
func (el *EventLog) GetRecent(n int) []EventRecord
GetRecent returns the N most recent events.
If n is greater than the number of events, all events are returned.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- n: Number of recent events to return
Returns:
- []EventRecord: The N most recent events
type EventRecord ¶
type EventRecord struct {
// SeqID is the auto-incrementing sequence ID for incremental exports
SeqID int64 `json:"seq_id"`
// ID is the unique identifier of the event
ID string
// Name is the event name (e.g., "click", "submit", "change")
Name string
// SourceID is the ID of the component that emitted the event
SourceID string
// TargetID is the ID of the component that received the event
TargetID string
// Payload is the event data
Payload interface{}
// Timestamp is when the event occurred
Timestamp time.Time
// Duration is how long the event handler took to execute
Duration time.Duration
}
EventRecord captures information about an event that occurred in the application.
Thread Safety:
Records are immutable after creation and safe to share across goroutines.
type EventReplayer ¶
type EventReplayer struct {
// contains filtered or unexported fields
}
EventReplayer replays captured events with speed control and pause/resume functionality.
It provides event replay capabilities for debugging and testing, allowing developers to replay captured events at different speeds, pause/resume playback, and track progress.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
events := []EventRecord{...}
replayer := NewEventReplayer(events)
replayer.SetSpeed(2.0) // 2x speed
cmd := replayer.Replay()
// Use cmd in Bubbletea Update() method
func NewEventReplayer ¶
func NewEventReplayer(events []EventRecord) *EventReplayer
NewEventReplayer creates a new event replayer with the given events.
The replayer starts with speed=1.0, not paused, and at index 0. Events are copied to prevent external modification.
Example:
events := []EventRecord{...}
replayer := NewEventReplayer(events)
Parameters:
- events: The events to replay
Returns:
- *EventReplayer: A new event replayer instance
func (*EventReplayer) GetProgress ¶
func (er *EventReplayer) GetProgress() (current int, total int)
GetProgress returns the current replay progress.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- current: Current event index
- total: Total number of events
func (*EventReplayer) GetSpeed ¶
func (er *EventReplayer) GetSpeed() float64
GetSpeed returns the current playback speed.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- float64: The current speed multiplier
func (*EventReplayer) IsPaused ¶
func (er *EventReplayer) IsPaused() bool
IsPaused returns whether the replay is currently paused.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- bool: True if paused, false otherwise
func (*EventReplayer) IsReplaying ¶
func (er *EventReplayer) IsReplaying() bool
IsReplaying returns whether replay is currently active.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- bool: True if replaying, false otherwise
func (*EventReplayer) Pause ¶
func (er *EventReplayer) Pause()
Pause pauses the replay.
While paused, no new events will be emitted. The current position is preserved and can be resumed later.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*EventReplayer) Replay ¶
func (er *EventReplayer) Replay() tea.Cmd
Replay starts replaying events and returns a Bubbletea command.
The command emits ReplayEventMsg for each event with appropriate timing based on the original event timestamps and the current speed setting.
If the event list is empty, returns nil immediately. If already replaying, returns nil.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
cmd := replayer.Replay()
if cmd != nil {
return m, cmd
}
Returns:
- tea.Cmd: The command to start replay, or nil if no events
func (*EventReplayer) Reset ¶
func (er *EventReplayer) Reset()
Reset resets the replayer to the beginning.
This stops any active replay and resets the position to index 0.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*EventReplayer) Resume ¶
func (er *EventReplayer) Resume() tea.Cmd
Resume resumes the replay from where it was paused.
If not currently paused, this is a no-op.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- tea.Cmd: Command to continue replay, or nil if not replaying
func (*EventReplayer) SetSpeed ¶
func (er *EventReplayer) SetSpeed(speed float64) error
SetSpeed sets the playback speed multiplier.
Speed must be greater than 0. Common values:
- 1.0: Normal speed (real-time)
- 2.0: Double speed (2x faster)
- 0.5: Half speed (slower)
- 10.0: Very fast
- 0.1: Very slow
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
err := replayer.SetSpeed(2.0) // 2x speed
if err != nil {
log.Printf("Invalid speed: %v", err)
}
Parameters:
- speed: The speed multiplier (must be > 0)
Returns:
- error: Error if speed is invalid
type EventStatistics ¶
type EventStatistics struct {
// TotalEvents is the total number of events captured
TotalEvents int
// EventsByName maps event names to their count
EventsByName map[string]int
// EventsBySource maps source IDs to their count
EventsBySource map[string]int
}
EventStatistics provides statistics about captured events.
type EventTracker ¶
type EventTracker struct {
// contains filtered or unexported fields
}
EventTracker captures and displays events in real-time.
It provides event capture with pause/resume functionality, filtering, and real-time display with Lipgloss styling. The tracker integrates with the EventLog from the Store.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
tracker := NewEventTracker(5000)
tracker.CaptureEvent(EventRecord{
ID: "event-1",
Name: "click",
SourceID: "button-1",
Timestamp: time.Now(),
})
output := tracker.Render()
func NewEventTracker ¶
func NewEventTracker(maxEvents int) *EventTracker
NewEventTracker creates a new event tracker with the specified maximum size.
The tracker starts unpaused with no filter applied.
Example:
tracker := NewEventTracker(5000) // Keep last 5000 events
Parameters:
- maxEvents: Maximum number of events to keep
Returns:
- *EventTracker: A new event tracker instance
func (*EventTracker) CaptureEvent ¶
func (et *EventTracker) CaptureEvent(event EventRecord)
CaptureEvent captures an event if not paused.
If the tracker is paused, the event is ignored. Otherwise, it is added to the event log.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
tracker.CaptureEvent(EventRecord{
ID: "event-1",
Name: "click",
SourceID: "button-1",
Timestamp: time.Now(),
Duration: time.Millisecond,
})
Parameters:
- event: The event to capture
func (*EventTracker) Clear ¶
func (et *EventTracker) Clear()
Clear removes all captured events.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*EventTracker) GetEventCount ¶
func (et *EventTracker) GetEventCount() int
GetEventCount returns the number of captured events.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- int: Number of events
func (*EventTracker) GetFilter ¶
func (et *EventTracker) GetFilter() string
GetFilter returns the current filter string.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- string: The current filter
func (*EventTracker) GetRecent ¶
func (et *EventTracker) GetRecent(n int) []EventRecord
GetRecent returns the N most recent events.
If n is greater than the number of events, all events are returned.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- n: Number of recent events to return
Returns:
- []EventRecord: The N most recent events
func (*EventTracker) GetStatistics ¶
func (et *EventTracker) GetStatistics() EventStatistics
GetStatistics returns statistics about captured events.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- EventStatistics: Event statistics
func (*EventTracker) IsPaused ¶
func (et *EventTracker) IsPaused() bool
IsPaused returns whether event capture is paused.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- bool: True if paused, false otherwise
func (*EventTracker) Pause ¶
func (et *EventTracker) Pause()
Pause pauses event capture.
While paused, calls to CaptureEvent will be ignored.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*EventTracker) Render ¶
func (et *EventTracker) Render() string
Render generates the visual output of the event tracker.
The output includes:
- A header with the title "Recent Events"
- Event list in reverse chronological order (newest first)
- Each event shows: timestamp, name, source → target, duration
- Filtered events based on the current filter
- Styled with Lipgloss for terminal display
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- string: The rendered output
func (*EventTracker) Resume ¶
func (et *EventTracker) Resume()
Resume resumes event capture.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*EventTracker) SetFilter ¶
func (et *EventTracker) SetFilter(filter string)
SetFilter sets the filter string for event names.
The filter is case-insensitive and matches substrings. An empty string clears the filter.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
tracker.SetFilter("click") // Show only events with "click" in name
tracker.SetFilter("") // Show all events
Parameters:
- filter: The filter string
type ExportCheckpoint ¶
type ExportCheckpoint struct {
// Timestamp is when this checkpoint was created
Timestamp time.Time `json:"timestamp"`
// LastEventID is the highest event ID included in this export
LastEventID int64 `json:"last_event_id"`
// LastStateID is the highest state change ID included in this export
LastStateID int64 `json:"last_state_id"`
// LastCommandID is the highest command ID included in this export
LastCommandID int64 `json:"last_command_id"`
// Version is the export format version
Version string `json:"version"`
}
ExportCheckpoint represents a point in time for incremental exports.
It tracks the highest IDs exported for each data type, allowing subsequent incremental exports to export only data created after this checkpoint.
Thread Safety:
ExportCheckpoint is a value type and safe to use concurrently after creation.
Example:
checkpoint := ExportCheckpoint{
Timestamp: time.Now(),
LastEventID: 100,
LastStateID: 50,
LastCommandID: 25,
Version: "1.0",
}
type ExportData ¶
type ExportData struct {
// Version is the export format version (currently "1.0")
Version string `json:"version"`
// Timestamp is when the export was created
Timestamp time.Time `json:"timestamp"`
// Components is the list of component snapshots (optional)
Components []*ComponentSnapshot `json:"components,omitempty"`
// State is the state change history (optional)
State []StateChange `json:"state,omitempty"`
// Events is the event log (optional)
Events []EventRecord `json:"events,omitempty"`
// Performance is the performance metrics (optional)
Performance *PerformanceData `json:"performance,omitempty"`
}
ExportData represents the complete debug data export format.
This structure is serialized to JSON when exporting dev tools data. It includes version information, timestamp, and optional sections for components, state history, events, and performance metrics.
Thread Safety:
ExportData is a value type and safe to use concurrently after creation.
Example:
data := ExportData{
Version: "1.0",
Timestamp: time.Now(),
Components: components,
State: stateHistory,
}
type ExportFormat ¶
type ExportFormat interface {
// Name returns the format name (e.g., "json", "yaml", "msgpack")
Name() string
// Extension returns the file extension including dot (e.g., ".json", ".yaml")
Extension() string
// ContentType returns the MIME content type (e.g., "application/json")
ContentType() string
// Marshal serializes ExportData to bytes
Marshal(data *ExportData) ([]byte, error)
// Unmarshal deserializes bytes to ExportData
Unmarshal([]byte, *ExportData) error
}
ExportFormat defines the interface for different export formats.
Implementations must provide marshaling and unmarshaling capabilities for ExportData, along with metadata about the format (name, extension, content type).
Thread Safety:
Implementations should be safe for concurrent use.
Example:
type MyFormat struct{}
func (f *MyFormat) Name() string { return "myformat" }
func (f *MyFormat) Extension() string { return ".myf" }
func (f *MyFormat) ContentType() string { return "application/myformat" }
func (f *MyFormat) Marshal(data *ExportData) ([]byte, error) { ... }
func (f *MyFormat) Unmarshal(b []byte, data *ExportData) error { ... }
type ExportOptions ¶
type ExportOptions struct {
// IncludeComponents determines if component snapshots are exported
IncludeComponents bool
// IncludeState determines if state change history is exported
IncludeState bool
// IncludeEvents determines if event log is exported
IncludeEvents bool
// IncludePerformance determines if performance metrics are exported
IncludePerformance bool
// Sanitize enables redaction of sensitive data
Sanitize bool
// RedactPatterns is a list of case-insensitive strings to redact
// Common patterns: "password", "token", "apikey", "secret"
RedactPatterns []string
// UseStreaming enables streaming mode for large exports.
// When true, data is processed incrementally with bounded memory usage.
// Automatically enabled for exports >10MB.
UseStreaming bool
// ProgressCallback is invoked periodically during streaming exports
// to report the number of bytes processed. Can be nil.
ProgressCallback func(bytesProcessed int64)
// Compress enables gzip compression for the export.
// When true, the output file will be compressed using gzip.
// The file will have gzip magic bytes (0x1f 0x8b) for auto-detection on import.
Compress bool
// CompressionLevel specifies the gzip compression level.
// Valid values:
// - gzip.NoCompression (0): No compression
// - gzip.BestSpeed (1): Fastest compression, larger files
// - gzip.DefaultCompression (-1): Balanced speed and size (default)
// - gzip.BestCompression (9): Maximum compression, slower
// Only used when Compress is true.
CompressionLevel int
}
ExportOptions configures what data to include in the export.
Use this to selectively export only the data you need, reducing file size and export time. Sanitization can be enabled to redact sensitive data before export.
Streaming mode is automatically enabled for large exports (>10MB) to prevent out-of-memory errors. You can also explicitly enable it for smaller exports if memory is constrained.
Example:
opts := ExportOptions{
IncludeComponents: true,
IncludeState: true,
Sanitize: true,
RedactPatterns: []string{"password", "token"},
UseStreaming: true,
ProgressCallback: func(bytes int64) {
fmt.Printf("Processed: %d bytes\n", bytes)
},
}
type FilterFunc ¶
type FilterFunc func(*ComponentSnapshot) bool
FilterFunc is a predicate function that determines if a component should be included in the filter results.
The function receives a ComponentSnapshot and returns true if the component should be included, false otherwise. This allows for custom filtering logic beyond type and status filtering.
Example:
// Filter components with more than 5 refs
customFilter := func(c *ComponentSnapshot) bool {
return len(c.Refs) > 5
}
type FlameGraphRenderer ¶
type FlameGraphRenderer struct {
// contains filtered or unexported fields
}
FlameGraphRenderer renders performance data as an ASCII flame graph.
Flame graphs visualize hierarchical performance data, showing which components consume the most time. Each bar's width is proportional to the time spent.
Example output:
Application ████████████████████████████████ 100% (50ms) ├─ Counter ████████████ 40% (20ms) ├─ Header ████████ 30% (15ms) └─ Footer ████ 20% (10ms)
Thread Safety:
FlameGraphRenderer is stateless and safe to use concurrently.
func NewFlameGraphRenderer ¶
func NewFlameGraphRenderer(width, height int) *FlameGraphRenderer
NewFlameGraphRenderer creates a new flame graph renderer.
Parameters:
- width: Total width for rendering bars (typically 80-120)
- height: Maximum depth to render (reserved for future use)
Returns:
- *FlameGraphRenderer: A new renderer instance
Example:
renderer := NewFlameGraphRenderer(80, 10) output := renderer.Render(performanceData)
func (*FlameGraphRenderer) Render ¶
func (fgr *FlameGraphRenderer) Render(data *PerformanceData) string
Render generates an ASCII flame graph from performance data.
The output shows a hierarchical view of component performance with:
- Root node showing total application time
- Child nodes for each component
- Bars proportional to time spent
- Colors indicating performance (green=fast, yellow=medium, red=slow)
- Percentages and absolute times
Parameters:
- data: The performance data to visualize
Returns:
- string: The rendered flame graph
Example:
data := NewPerformanceData()
data.RecordRender("comp-1", "Counter", 20*time.Millisecond)
renderer := NewFlameGraphRenderer(80, 10)
fmt.Println(renderer.Render(data))
type FlameNode ¶
type FlameNode struct {
// Name is the component or section name
Name string
// Time is the duration spent in this node
Time time.Duration
// TimePercentage is the percentage of parent's time (0-100)
TimePercentage float64
// Children are the child nodes
Children []*FlameNode
}
FlameNode represents a node in the flame graph tree.
Each node has a name, time duration, percentage of total time, and children.
type FocusTarget ¶
type FocusTarget int
FocusTarget represents which part of the dev tools has focus.
Focus determines which keyboard shortcuts are active and where keyboard input is directed.
const ( // FocusApp indicates the main application has focus. FocusApp FocusTarget = iota // FocusTools indicates the dev tools panel has focus. FocusTools // FocusInspector indicates the component inspector has focus. FocusInspector // FocusState indicates the state viewer has focus. FocusState // FocusEvents indicates the event tracker has focus. FocusEvents // FocusPerformance indicates the performance monitor has focus. FocusPerformance )
type FormatRegistry ¶
type FormatRegistry struct {
// contains filtered or unexported fields
}
FormatRegistry is a thread-safe registry of export formats.
The registry maps format names to their implementations. It provides methods to register new formats, retrieve formats by name, and list all supported formats.
Thread Safety:
All methods are safe for concurrent use.
Example:
registry := NewFormatRegistry()
registry.Register(&JSONFormat{})
format, err := registry.Get("json")
func NewFormatRegistry ¶
func NewFormatRegistry() *FormatRegistry
NewFormatRegistry creates a new empty format registry.
Thread Safety:
Safe for concurrent use.
func (*FormatRegistry) Get ¶
func (r *FormatRegistry) Get(name string) (ExportFormat, error)
Get retrieves a format by name.
Thread Safety:
Safe for concurrent use.
Parameters:
- name: The format name (case-insensitive)
Returns:
- ExportFormat: The format implementation
- error: nil on success, error if format not found
func (*FormatRegistry) GetAll ¶
func (r *FormatRegistry) GetAll() map[string]ExportFormat
GetAll returns all registered formats.
Thread Safety:
Safe for concurrent use. Returns a copy of the formats map.
Returns:
- map[string]ExportFormat: Map of format names to implementations
func (*FormatRegistry) Register ¶
func (r *FormatRegistry) Register(format ExportFormat)
Register adds a format to the registry.
If a format with the same name already exists, it will be replaced.
Thread Safety:
Safe for concurrent use.
Parameters:
- format: The ExportFormat implementation to register
type GuardExecution ¶
type GuardExecution struct {
Name string // Guard function name
Result GuardResult // Guard decision
Timestamp time.Time // When guard executed
Duration time.Duration // Guard execution duration
}
GuardExecution captures a navigation guard execution event.
GuardExecution stores information about a single guard execution, including the guard name, result, and timing information. This is used by the RouterDebugger to trace guard execution during navigation.
Fields:
- Name: The name of the guard function
- Result: The guard's decision (Allow, Cancel, or Redirect)
- Timestamp: When the guard executed
- Duration: How long the guard took to execute
Example:
execution := GuardExecution{
Name: "authGuard",
Result: GuardAllow,
Timestamp: time.Now(),
Duration: 1 * time.Millisecond,
}
type GuardResult ¶
type GuardResult int
GuardResult represents the result of a navigation guard execution.
const ( // GuardAllow indicates the guard allowed navigation to proceed. GuardAllow GuardResult = iota // GuardCancel indicates the guard canceled navigation. GuardCancel // GuardRedirect indicates the guard redirected to a different route. GuardRedirect )
func (GuardResult) String ¶
func (gr GuardResult) String() string
String returns the string representation of a GuardResult.
type IncrementalExportData ¶
type IncrementalExportData struct {
// Checkpoint is the previous checkpoint this delta is based on
Checkpoint ExportCheckpoint `json:"checkpoint"`
// NewEvents contains events created since the checkpoint
NewEvents []EventRecord `json:"new_events,omitempty"`
// NewState contains state changes since the checkpoint
NewState []StateChange `json:"new_state,omitempty"`
// NewCommands contains commands since the checkpoint
NewCommands []CommandRecord `json:"new_commands,omitempty"`
}
IncrementalExportData represents a delta export containing only new data.
This structure contains only the data that has been created since the previous checkpoint, making it much smaller than a full export for long-running sessions.
Thread Safety:
IncrementalExportData is a value type and safe to use concurrently after creation.
Example:
delta := IncrementalExportData{
Checkpoint: previousCheckpoint,
NewEvents: []EventRecord{...},
NewState: []StateChange{...},
NewCommands: []CommandRecord{...},
}
type Instrumentor ¶
type Instrumentor struct {
// contains filtered or unexported fields
}
Instrumentor manages instrumentation hooks for collecting debug data from the application.
The instrumentor acts as a bridge between the application code (components, refs, events) and the dev tools data collector. It provides a global singleton that application code can call at key lifecycle points without needing to know about the collector directly.
When dev tools are disabled (collector is nil), all instrumentation calls become no-ops with minimal overhead (just a nil check).
Thread Safety:
All methods are thread-safe and can be called concurrently from multiple goroutines.
Example:
// In component lifecycle devtools.NotifyComponentMounted(component.ID()) // In ref.Set() devtools.NotifyRefChanged(ref.id, oldValue, newValue)
type JSONFormat ¶
type JSONFormat struct{}
JSONFormat implements ExportFormat for JSON.
Uses the standard library encoding/json package for marshaling and unmarshaling. Output is indented for readability.
Thread Safety:
Safe for concurrent use.
func (*JSONFormat) ContentType ¶
func (f *JSONFormat) ContentType() string
ContentType returns "application/json".
func (*JSONFormat) Marshal ¶
func (f *JSONFormat) Marshal(data *ExportData) ([]byte, error)
Marshal serializes ExportData to JSON with indentation.
Parameters:
- data: The ExportData to marshal
Returns:
- []byte: JSON bytes
- error: nil on success, error on marshal failure
func (*JSONFormat) Unmarshal ¶
func (f *JSONFormat) Unmarshal(b []byte, data *ExportData) error
Unmarshal deserializes JSON bytes to ExportData.
Parameters:
- b: JSON bytes to unmarshal
- data: Pointer to ExportData to populate
Returns:
- error: nil on success, error on unmarshal failure
type KeyHandler ¶
KeyHandler is a function that handles a keyboard message and optionally returns a Bubbletea command.
Handlers can: - Process the key message - Update application state - Return commands for async operations - Return nil if no command needed
type KeyboardHandler ¶
type KeyboardHandler struct {
// contains filtered or unexported fields
}
KeyboardHandler manages keyboard shortcuts and focus for dev tools.
The keyboard handler supports: - Global shortcuts (work with any focus) - Focus-specific shortcuts (only work when specific panel has focus) - Dynamic shortcut registration/unregistration - Thread-safe concurrent access
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
kh := devtools.NewKeyboardHandler()
// Register global F12 toggle
kh.RegisterGlobal("f12", func(msg tea.KeyMsg) tea.Cmd {
devtools.Toggle()
return nil
})
// Register inspector-specific shortcut
kh.RegisterWithFocus("ctrl+f", FocusInspector, func(msg tea.KeyMsg) tea.Cmd {
// Open search in inspector
return nil
})
// Handle keyboard message
cmd := kh.Handle(keyMsg)
func NewKeyboardHandler ¶
func NewKeyboardHandler() *KeyboardHandler
NewKeyboardHandler creates a new keyboard handler with default focus on the app.
func (*KeyboardHandler) GetFocus ¶
func (kh *KeyboardHandler) GetFocus() FocusTarget
GetFocus returns the current focus target.
func (*KeyboardHandler) Handle ¶
func (kh *KeyboardHandler) Handle(msg tea.KeyMsg) tea.Cmd
Handle processes a keyboard message and calls the appropriate handler.
The handler is selected based on: 1. Global handlers are always checked first 2. Focus-specific handlers are checked if focus matches 3. First matching handler is called
Returns the command from the handler, or nil if no handler matched or the handler returned nil.
func (*KeyboardHandler) Register ¶
func (kh *KeyboardHandler) Register(key string, handler KeyHandler)
Register registers a keyboard shortcut handler.
The handler will be called when the specified key is pressed, regardless of current focus (global shortcut).
If key is empty or handler is nil, the registration is ignored.
func (*KeyboardHandler) RegisterGlobal ¶
func (kh *KeyboardHandler) RegisterGlobal(key string, handler KeyHandler)
RegisterGlobal registers a global keyboard shortcut that works regardless of focus.
Global shortcuts are useful for: - Toggle dev tools visibility (F12) - Quit application (Ctrl+C) - Help dialog (?)
If key is empty or handler is nil, the registration is ignored.
func (*KeyboardHandler) RegisterWithFocus ¶
func (kh *KeyboardHandler) RegisterWithFocus(key string, focus FocusTarget, handler KeyHandler)
RegisterWithFocus registers a keyboard shortcut that only works when the specified focus target is active.
Focus-specific shortcuts are useful for: - Panel-specific navigation - Context-sensitive actions - Avoiding key conflicts between panels
If key is empty or handler is nil, the registration is ignored.
func (*KeyboardHandler) SetFocus ¶
func (kh *KeyboardHandler) SetFocus(focus FocusTarget)
SetFocus changes the current focus target.
Changing focus affects which keyboard shortcuts are active. Only focus-specific shortcuts for the new focus will respond to keys. Global shortcuts continue to work regardless of focus.
func (*KeyboardHandler) Unregister ¶
func (kh *KeyboardHandler) Unregister(key string)
Unregister removes all handlers for the specified key.
This is useful for: - Disabling shortcuts temporarily - Changing shortcut behavior dynamically - Cleaning up on panel close
type LayoutManager ¶
type LayoutManager struct {
// contains filtered or unexported fields
}
LayoutManager manages the layout of the dev tools UI. It supports multiple layout modes (horizontal, vertical, overlay, hidden) and configurable split ratios for positioning the dev tools relative to the application content.
func NewLayoutManager ¶
func NewLayoutManager(mode LayoutMode, ratio float64) *LayoutManager
NewLayoutManager creates a new LayoutManager with the specified mode and ratio. The ratio determines how much space the application takes (0.0 = none, 1.0 = all). Default ratio is 0.6 (60% app, 40% tools).
func (*LayoutManager) GetMode ¶
func (lm *LayoutManager) GetMode() LayoutMode
GetMode returns the current layout mode.
func (*LayoutManager) GetRatio ¶
func (lm *LayoutManager) GetRatio() float64
GetRatio returns the current split ratio.
func (*LayoutManager) GetSize ¶
func (lm *LayoutManager) GetSize() (width, height int)
GetSize returns the current width and height.
func (*LayoutManager) Render ¶
func (lm *LayoutManager) Render(appContent, toolsContent string) string
Render renders the application and dev tools content according to the current layout mode. Returns the final rendered output as a string.
func (*LayoutManager) SetMode ¶
func (lm *LayoutManager) SetMode(mode LayoutMode)
SetMode sets the layout mode.
func (*LayoutManager) SetRatio ¶
func (lm *LayoutManager) SetRatio(ratio float64)
SetRatio sets the split ratio (0.0 - 1.0). The ratio determines how much space the application takes.
func (*LayoutManager) SetSize ¶
func (lm *LayoutManager) SetSize(width, height int)
SetSize sets the total available width and height.
type LayoutMode ¶
type LayoutMode int
LayoutMode defines how dev tools UI is displayed relative to the application.
const ( // LayoutHorizontal displays dev tools side-by-side with the application (default) LayoutHorizontal LayoutMode = iota // LayoutVertical displays dev tools stacked vertically with the application LayoutVertical // LayoutOverlay displays dev tools as an overlay on top of the application LayoutOverlay // LayoutHidden hides the dev tools UI (still collecting data if enabled) LayoutHidden )
func CalculateResponsiveLayout ¶
func CalculateResponsiveLayout(width int) (LayoutMode, float64)
CalculateResponsiveLayout determines the optimal layout mode and ratio based on terminal width.
Breakpoints:
- < 80 cols: Vertical layout, 50/50 split (too narrow for side-by-side)
- 80-120 cols: Horizontal layout, 50/50 split (medium width)
- > 120 cols: Horizontal layout, 40/60 split (wide, more space for tools)
Thread Safety:
Safe to call concurrently (pure function, no shared state).
Example:
mode, ratio := devtools.CalculateResponsiveLayout(100) // mode = LayoutHorizontal, ratio = 0.5
Parameters:
- width: Terminal width in columns
Returns:
- LayoutMode: The recommended layout mode
- float64: The recommended split ratio (app size / total size)
func (LayoutMode) String ¶
func (lm LayoutMode) String() string
String returns the string representation of the layout mode.
type MatchLocation ¶
type MatchLocation struct {
// Path is the location of the match in the data structure
// Format: "components[0].props.password" or "state[1].new_value"
Path string
// Pattern is the name of the pattern that matched
Pattern string
// Original is the original value before redaction (may be truncated)
Original string
// Redacted is what the value would become after redaction
Redacted string
// Line is the line number in JSON output (0 if not applicable)
Line int
// Column is the column number in JSON output (0 if not applicable)
Column int
}
MatchLocation describes a single match found during dry-run sanitization.
It includes the path to the matched value, the pattern that matched, the original value, and what the redacted value would be. This allows developers to review exactly what would be changed before applying sanitization.
Example:
match := MatchLocation{
Path: "components[0].props.password",
Pattern: "password",
Original: "secret123",
Redacted: "[REDACTED]",
}
type MessagePackFormat ¶
type MessagePackFormat struct{}
MessagePackFormat implements ExportFormat for MessagePack.
Uses github.com/vmihailenco/msgpack/v5 for marshaling and unmarshaling. MessagePack is a binary format that is more compact than JSON and YAML.
Thread Safety:
Safe for concurrent use.
func (*MessagePackFormat) ContentType ¶
func (f *MessagePackFormat) ContentType() string
ContentType returns "application/msgpack".
func (*MessagePackFormat) Extension ¶
func (f *MessagePackFormat) Extension() string
Extension returns ".msgpack".
func (*MessagePackFormat) Marshal ¶
func (f *MessagePackFormat) Marshal(data *ExportData) ([]byte, error)
Marshal serializes ExportData to MessagePack.
Parameters:
- data: The ExportData to marshal
Returns:
- []byte: MessagePack bytes
- error: nil on success, error on marshal failure
func (*MessagePackFormat) Unmarshal ¶
func (f *MessagePackFormat) Unmarshal(b []byte, data *ExportData) error
Unmarshal deserializes MessagePack bytes to ExportData.
Parameters:
- b: MessagePack bytes to unmarshal
- data: Pointer to ExportData to populate
Returns:
- error: nil on success, error on unmarshal failure
type PerformanceData ¶
type PerformanceData struct {
// contains filtered or unexported fields
}
PerformanceData tracks performance metrics for components.
Thread Safety:
All methods are thread-safe and can be called concurrently.
func NewPerformanceData ¶
func NewPerformanceData() *PerformanceData
NewPerformanceData creates a new performance data tracker.
Returns:
- *PerformanceData: A new performance data instance
func (*PerformanceData) Clear ¶
func (pd *PerformanceData) Clear()
Clear removes all performance data.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*PerformanceData) GetAll ¶
func (pd *PerformanceData) GetAll() map[string]*ComponentPerformance
GetAll returns performance metrics for all components.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- map[string]*ComponentPerformance: All component performance metrics
func (*PerformanceData) GetComponent ¶
func (pd *PerformanceData) GetComponent(componentID string) *ComponentPerformance
GetComponent returns performance metrics for a specific component.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- componentID: The component ID
Returns:
- *ComponentPerformance: The performance metrics, or nil if not found
func (*PerformanceData) RecordRender ¶
func (pd *PerformanceData) RecordRender(componentID, componentName string, duration time.Duration)
RecordRender records a component render with its duration.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- componentID: The component ID
- componentName: The component name
- duration: How long the render took
type PerformanceHook ¶
type PerformanceHook interface {
// OnRenderComplete is called when a component finishes rendering
OnRenderComplete(componentID string, duration time.Duration)
}
PerformanceHook is called when performance metrics are collected.
Implementations must be thread-safe as hooks can be called concurrently from multiple goroutines.
Example:
type MyPerfHook struct{}
func (h *MyPerfHook) OnRenderComplete(componentID string, duration time.Duration) {
fmt.Printf("Component %s rendered in %v\n", componentID, duration)
}
type PerformanceMonitor ¶
type PerformanceMonitor struct {
// contains filtered or unexported fields
}
PerformanceMonitor tracks and displays component performance metrics.
It provides methods to record render timing, sort components by various metrics, and render a formatted table of performance data. The monitor integrates with PerformanceData from the Store.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
data := NewPerformanceData()
monitor := NewPerformanceMonitor(data)
monitor.RecordRender("comp-1", "Counter", 5*time.Millisecond)
output := monitor.Render(SortByAvgTime)
func NewPerformanceMonitor ¶
func NewPerformanceMonitor(data *PerformanceData) *PerformanceMonitor
NewPerformanceMonitor creates a new performance monitor.
The monitor starts with SortByRenderCount as the default sort order.
Example:
data := NewPerformanceData() monitor := NewPerformanceMonitor(data)
Parameters:
- data: The performance data store to use
Returns:
- *PerformanceMonitor: A new performance monitor instance
func (*PerformanceMonitor) GetSortBy ¶
func (pm *PerformanceMonitor) GetSortBy() SortBy
GetSortBy returns the current sort order.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- SortBy: The current sort order
func (*PerformanceMonitor) GetSortedComponents ¶
func (pm *PerformanceMonitor) GetSortedComponents(sortBy SortBy) []*ComponentPerformance
GetSortedComponents returns all components sorted by the specified order.
The sorting is performed on a copy of the data, so the original data is not modified. Components are sorted in descending order (highest first).
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- sortBy: How to sort the components
Returns:
- []*ComponentPerformance: Sorted component performance metrics
func (*PerformanceMonitor) RecordRender ¶
func (pm *PerformanceMonitor) RecordRender(componentID, componentName string, duration time.Duration)
RecordRender records a component render with its duration.
This is a convenience method that forwards to PerformanceData.RecordRender. It maintains < 2% overhead as required by the specifications.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- componentID: The component ID
- componentName: The component name
- duration: How long the render took
func (*PerformanceMonitor) Render ¶
func (pm *PerformanceMonitor) Render(sortBy SortBy) string
Render generates a formatted table of performance metrics.
The output includes:
- A header with the title "Component Performance"
- A table with columns: Component, Renders, Avg Time, Max Time
- Components sorted by the specified order
- Styled with Lipgloss for terminal display
- Empty message if no performance data exists
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- sortBy: How to sort the components in the output
Returns:
- string: The rendered output
func (*PerformanceMonitor) SetSortBy ¶
func (pm *PerformanceMonitor) SetSortBy(sortBy SortBy)
SetSortBy sets the default sort order for rendering.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- sortBy: The sort order to use
type RefInterface ¶
type RefInterface interface {
// GetID returns the ref's unique ID
GetID() string
// GetName returns the ref's variable name
GetName() string
// GetValue returns the current value
GetValue() interface{}
// GetType returns the Go type name
GetType() string
// GetWatcherCount returns the number of active watchers
GetWatcherCount() int
}
RefInterface defines the interface for reactive references.
type RefSnapshot ¶
type RefSnapshot struct {
// ID is the unique identifier of the ref
ID string
// Name is the variable name of the ref
Name string
// Type is the Go type of the ref's value
Type string
// Value is the current value of the ref
Value interface{}
// Watchers is the number of active watchers on this ref
Watchers int
}
RefSnapshot captures the state of a reactive reference at a specific point in time.
Thread Safety:
Snapshots are immutable after creation and safe to share across goroutines.
type ReplayCommandMsg ¶
type ReplayCommandMsg struct {
// Command is the replayed command
Command CommandRecord
// Index is the current index in the replay sequence
Index int
// Total is the total number of commands in the replay
Total int
// NextCmd is the command to execute for the next command (or nil if done)
NextCmd tea.Cmd
}
ReplayCommandMsg is the message sent when a command is replayed.
This message is sent to the Bubbletea Update() method for each replayed command. Applications can distinguish replayed commands from real commands by checking the message type.
type ReplayCompletedMsg ¶
type ReplayCompletedMsg struct {
// TotalEvents is the number of events replayed
TotalEvents int
}
ReplayCompletedMsg is sent when replay completes.
type ReplayEventMsg ¶
type ReplayEventMsg struct {
// Event is the replayed event
Event EventRecord
// Index is the current index in the replay sequence
Index int
// Total is the total number of events in the replay
Total int
// NextCmd is the command to execute for the next event (or nil if done)
NextCmd tea.Cmd
}
ReplayEventMsg is the message sent when an event is replayed.
This message is sent to the Bubbletea Update() method for each replayed event. Applications can distinguish replayed events from real events by checking the message type.
type ReplayPausedMsg ¶
type ReplayPausedMsg struct {
// Index is the current index when paused
Index int
// Total is the total number of events
Total int
}
ReplayPausedMsg is sent when replay is paused.
type RouteRecord ¶
type RouteRecord struct {
From *router.Route // Source route (nil for initial navigation)
To *router.Route // Destination route
Timestamp time.Time // When navigation occurred
Duration time.Duration // Navigation duration
Success bool // Whether navigation succeeded
}
RouteRecord captures a navigation event between routes.
RouteRecord stores information about a single navigation event, including the source and destination routes, timing information, and success status. This is used by the RouterDebugger to track navigation history.
Fields:
- From: The route being navigated away from (nil for initial navigation)
- To: The route being navigated to
- Timestamp: When the navigation occurred
- Duration: How long the navigation took
- Success: Whether the navigation completed successfully
Example:
record := RouteRecord{
From: previousRoute,
To: newRoute,
Timestamp: time.Now(),
Duration: 5 * time.Millisecond,
Success: true,
}
type RouterDebugger ¶
type RouterDebugger struct {
// contains filtered or unexported fields
}
RouterDebugger tracks and displays router navigation state and history.
RouterDebugger provides debugging capabilities for the router system, including current route inspection, navigation history tracking, and guard execution tracing. It maintains a circular buffer of navigation records and guard executions for debugging purposes.
Thread Safety: All methods are thread-safe and can be called concurrently from multiple goroutines. Internal state is protected by a sync.RWMutex.
Usage:
debugger := NewRouterDebugger(100)
// Record navigation
debugger.RecordNavigation(fromRoute, toRoute, 5*time.Millisecond, true)
// Record guard execution
debugger.RecordGuard("authGuard", GuardAllow, 1*time.Millisecond)
// Display debug information
output := debugger.Render()
fmt.Println(output)
func NewRouterDebugger ¶
func NewRouterDebugger(maxSize int) *RouterDebugger
NewRouterDebugger creates a new RouterDebugger with the specified history size.
The debugger maintains circular buffers for navigation history and guard executions. When the buffers reach maxSize, the oldest entries are removed to make room for new ones.
Parameters:
- maxSize: Maximum number of records to keep in history buffers
Returns:
- *RouterDebugger: A new debugger instance
Example:
debugger := NewRouterDebugger(100)
func (*RouterDebugger) Clear ¶
func (rd *RouterDebugger) Clear()
Clear removes all navigation history and guard executions.
This method resets the debugger to its initial state, clearing the current route, navigation history, and guard execution history.
Thread Safety: This method is thread-safe and can be called concurrently.
func (*RouterDebugger) GetCurrentRoute ¶
func (rd *RouterDebugger) GetCurrentRoute() *router.Route
GetCurrentRoute returns the current active route.
Returns:
- *router.Route: The current route, or nil if no route is active
Thread Safety: This method is thread-safe and can be called concurrently.
func (*RouterDebugger) GetGuards ¶
func (rd *RouterDebugger) GetGuards() []GuardExecution
GetGuards returns a copy of the guard execution history.
The returned slice is a defensive copy to prevent external modification of the internal state.
Returns:
- []GuardExecution: Copy of guard execution history (most recent last)
Thread Safety: This method is thread-safe and can be called concurrently.
func (*RouterDebugger) GetHistory ¶
func (rd *RouterDebugger) GetHistory() []RouteRecord
GetHistory returns a copy of the navigation history.
The returned slice is a defensive copy to prevent external modification of the internal state.
Returns:
- []RouteRecord: Copy of navigation history (most recent last)
Thread Safety: This method is thread-safe and can be called concurrently.
func (*RouterDebugger) GetHistoryCount ¶
func (rd *RouterDebugger) GetHistoryCount() int
GetHistoryCount returns the number of navigation records in history.
Returns:
- int: Number of navigation records
Thread Safety: This method is thread-safe and can be called concurrently.
func (*RouterDebugger) RecordGuard ¶
func (rd *RouterDebugger) RecordGuard(guardName string, result GuardResult, duration time.Duration)
RecordGuard records a navigation guard execution.
This method captures a guard execution event, including the guard name, result, and timing information. The execution is added to the guard history buffer.
Parameters:
- guardName: The name of the guard function
- result: The guard's decision (Allow, Cancel, or Redirect)
- duration: How long the guard took to execute
Thread Safety: This method is thread-safe and can be called concurrently.
Example:
debugger.RecordGuard("authGuard", GuardAllow, 1*time.Millisecond)
func (*RouterDebugger) RecordNavigation ¶
func (rd *RouterDebugger) RecordNavigation(from, to *router.Route, duration time.Duration, success bool)
RecordNavigation records a navigation event.
This method captures a navigation from one route to another, including timing and success information. The navigation is added to the history buffer, and the current route is updated to the destination route.
Parameters:
- from: The source route (nil for initial navigation)
- to: The destination route
- duration: How long the navigation took
- success: Whether the navigation succeeded
Thread Safety: This method is thread-safe and can be called concurrently.
Example:
debugger.RecordNavigation(homeRoute, aboutRoute, 5*time.Millisecond, true)
func (*RouterDebugger) Render ¶
func (rd *RouterDebugger) Render() string
Render generates a styled text representation of the router debug state.
The output includes:
- Current route information (path, name, params, query, hash)
- Navigation history with timestamps and success indicators
- Guard execution trace with results and timing
Returns:
- string: Styled debug output using Lipgloss
Thread Safety: This method is thread-safe and can be called concurrently.
Example:
output := debugger.Render() fmt.Println(output)
type SanitizationStats ¶
type SanitizationStats struct {
// RedactedCount is the total number of values redacted
RedactedCount int `json:"redacted_count"`
// PatternMatches maps pattern names to their match counts
PatternMatches map[string]int `json:"pattern_matches"`
// Duration is how long the sanitization took
Duration time.Duration `json:"duration_ms"`
// BytesProcessed is the total number of bytes processed
BytesProcessed int64 `json:"bytes_processed"`
// StartTime is when sanitization started
StartTime time.Time `json:"start_time"`
// EndTime is when sanitization completed
EndTime time.Time `json:"end_time"`
}
SanitizationStats tracks statistics from a sanitization operation.
It records the number of values redacted, which patterns matched, how long the operation took, and how many bytes were processed. This information is useful for auditing, performance monitoring, and validating sanitization effectiveness.
Example:
sanitizer := NewSanitizer()
_ = sanitizer.SanitizeString(`{"password": "secret"}`)
stats := sanitizer.GetLastStats()
fmt.Println(stats.String())
// Output: Redacted 1 values: pattern_0=1 (5ms)
func (*SanitizationStats) JSON ¶
func (s *SanitizationStats) JSON() ([]byte, error)
JSON returns a JSON representation of the stats.
The JSON includes all fields with appropriate types:
- redacted_count: number
- pattern_matches: object mapping pattern names to counts
- duration_ms: number (milliseconds)
- bytes_processed: number
- start_time: ISO 8601 timestamp
- end_time: ISO 8601 timestamp
Example:
stats := &SanitizationStats{
RedactedCount: 10,
PatternMatches: map[string]int{"password": 5, "token": 5},
Duration: 100 * time.Millisecond,
BytesProcessed: 1024,
}
data, _ := stats.JSON()
fmt.Println(string(data))
// Output: {"redacted_count":10,"pattern_matches":{"password":5,"token":5},...}
Returns:
- []byte: JSON-encoded stats
- error: Error if JSON encoding fails
func (*SanitizationStats) String ¶
func (s *SanitizationStats) String() string
String returns a human-readable representation of the stats.
Format: "Redacted N values: pattern1=X, pattern2=Y (duration)"
Example:
stats := &SanitizationStats{
RedactedCount: 47,
PatternMatches: map[string]int{"password": 23, "token": 15, "apikey": 9},
Duration: 142 * time.Millisecond,
}
fmt.Println(stats.String())
// Output: Redacted 47 values: apikey=9, password=23, token=15 (142ms)
Returns:
- string: Human-readable stats summary
type SanitizeOptions ¶
type SanitizeOptions struct {
// DryRun enables preview mode - matches are collected but data is not modified
DryRun bool
// MaxPreviewLen truncates original values longer than this in match locations.
// Set to 0 for no truncation. Default is 100 characters.
MaxPreviewLen int
}
SanitizeOptions configures sanitization behavior.
Use DryRun: true to preview matches without modifying data. Use MaxPreviewLen to truncate long values in preview output.
Example:
opts := SanitizeOptions{
DryRun: true,
MaxPreviewLen: 100,
}
_, dryRunResult := sanitizer.SanitizeWithOptions(data, opts)
type SanitizePattern ¶
type SanitizePattern struct {
// Pattern is the compiled regular expression to match
Pattern *regexp.Regexp
// Replacement is the string to replace matches with
Replacement string
// Priority determines the order in which patterns are applied.
// Higher priority patterns are applied first. Default is 0.
Priority int
// Name is an optional identifier for the pattern, useful for
// tracking, debugging, and audit trails. If empty, a name will
// be auto-generated in the format "pattern_N".
Name string
}
SanitizePattern represents a single sanitization rule with priority ordering.
It contains a compiled regular expression pattern, the replacement string to use when the pattern matches, a priority for ordering, and an optional name for tracking/debugging.
Priority Ranges:
- 100+: Critical patterns (e.g., PCI, HIPAA compliance)
- 50-99: Organization-specific patterns
- 10-49: Custom patterns
- 0-9: Default patterns
- Negative: Cleanup patterns (apply last)
Higher priority patterns are applied first. When priorities are equal, patterns are applied in insertion order (stable sort).
Example:
pattern := SanitizePattern{
Pattern: regexp.MustCompile(`(?i)api[_-]?key["\s:=]+\S+`),
Replacement: "[REDACTED]",
Priority: 50,
Name: "api_key",
}
type Sanitizer ¶
type Sanitizer struct {
// contains filtered or unexported fields
}
Sanitizer provides regex-based sanitization of sensitive data in exports.
It uses regular expression patterns to identify and redact sensitive information like passwords, tokens, API keys, and secrets. The sanitizer can handle nested data structures including maps, slices, and structs.
Thread Safety:
Sanitizer is safe to use concurrently after creation. The patterns slice should not be modified after creation.
Example:
sanitizer := devtools.NewSanitizer() sanitizer.AddPattern(`(?i)password["\s:=]+\S+`, "[REDACTED]") cleanData := sanitizer.Sanitize(exportData)
func NewSanitizer ¶
func NewSanitizer() *Sanitizer
NewSanitizer creates a new sanitizer with default patterns.
The default patterns cover common sensitive data:
- Passwords (password, passwd, pwd)
- Tokens (token, bearer)
- API keys (api_key, apikey, api-key)
- Secrets (secret, private_key, private-key)
All patterns are case-insensitive and match common formats like:
- JSON: "password": "secret123"
- URL params: password=secret123
- Headers: Authorization: Bearer token123
Example:
sanitizer := devtools.NewSanitizer() cleanData := sanitizer.Sanitize(exportData)
Returns:
- *Sanitizer: A new sanitizer with default patterns
func (*Sanitizer) AddPattern ¶
AddPattern adds a new sanitization pattern to the sanitizer with default priority (0).
The pattern string is compiled as a regular expression. If compilation fails, this method panics. Use this during initialization when you want the program to fail fast on invalid patterns.
Example:
sanitizer := devtools.NewSanitizer() sanitizer.AddPattern(`(?i)credit[_-]?card["\s:=]+\d+`, "[REDACTED]")
Parameters:
- pattern: Regular expression pattern string
- replacement: String to replace matches with
func (*Sanitizer) AddPatternWithPriority ¶
func (s *Sanitizer) AddPatternWithPriority(pattern, replacement string, priority int, name string) error
AddPatternWithPriority adds a new sanitization pattern with explicit priority and name.
The pattern string is compiled as a regular expression. If compilation fails, this method returns an error instead of panicking, making it suitable for runtime pattern addition.
Priority determines the order in which patterns are applied:
- 100+: Critical patterns (PCI, HIPAA compliance)
- 50-99: Organization-specific patterns
- 10-49: Custom patterns
- 0-9: Default patterns
- Negative: Cleanup patterns (apply last)
Higher priority patterns are applied first. When priorities are equal, patterns are applied in insertion order (stable sort).
If name is empty, a name will be auto-generated in the format "pattern_N".
Example:
sanitizer := devtools.NewSanitizer()
err := sanitizer.AddPatternWithPriority(
`(?i)(merchant[_-]?id)(["'\s:=]+)([A-Z0-9]+)`,
"${1}${2}[REDACTED_MERCHANT]",
80,
"merchant_id",
)
if err != nil {
log.Fatal(err)
}
Parameters:
- pattern: Regular expression pattern string
- replacement: String to replace matches with
- priority: Priority for pattern ordering (higher applies first)
- name: Optional name for tracking/debugging (auto-generated if empty)
Returns:
- error: Error if pattern compilation fails
func (*Sanitizer) GetLastStats ¶
func (s *Sanitizer) GetLastStats() *SanitizationStats
GetLastStats returns the statistics from the most recent sanitization.
Returns nil if no sanitization has been performed yet. The returned stats are a snapshot and will not be updated by subsequent sanitizations.
Thread Safety:
Safe to call concurrently. Uses read lock for thread-safe access.
Example:
sanitizer := NewSanitizer()
_ = sanitizer.SanitizeString(`{"password": "secret"}`)
stats := sanitizer.GetLastStats()
if stats != nil {
fmt.Printf("Redacted %d values in %v\n", stats.RedactedCount, stats.Duration)
}
Returns:
- *SanitizationStats: Stats from last sanitization, or nil if none performed
func (*Sanitizer) GetPatterns ¶
func (s *Sanitizer) GetPatterns() []SanitizePattern
GetPatterns returns a copy of all patterns in sorted order (by priority, descending).
The returned slice is a copy, so modifications will not affect the sanitizer. Patterns are sorted by priority (highest first), with equal priorities maintaining insertion order.
Example:
sanitizer := devtools.NewSanitizer()
patterns := sanitizer.GetPatterns()
for _, p := range patterns {
fmt.Printf("Pattern: %s, Priority: %d\n", p.Name, p.Priority)
}
Returns:
- []SanitizePattern: Copy of patterns in priority order
func (*Sanitizer) LoadTemplate ¶
LoadTemplate loads a pre-configured template by name and appends its patterns.
This method is composable - calling it multiple times will append patterns from each template. Patterns are applied in priority order when sanitizing.
Available templates: "pii", "pci", "hipaa", "gdpr"
Example:
sanitizer := devtools.NewSanitizer()
err := sanitizer.LoadTemplate("pii")
if err != nil {
log.Fatal(err)
}
// Now sanitizer has default patterns + PII patterns
Parameters:
- name: Template name (case-sensitive)
Returns:
- error: Error if template name is invalid
func (*Sanitizer) LoadTemplates ¶
LoadTemplates loads multiple templates at once and appends all their patterns.
This is a convenience method for loading multiple compliance templates. Patterns from all templates are combined and will be applied in priority order.
Example:
sanitizer := devtools.NewSanitizer()
err := sanitizer.LoadTemplates("pii", "pci", "hipaa")
if err != nil {
log.Fatal(err)
}
// Now sanitizer has patterns from all three templates
Parameters:
- names: Template names to load (case-sensitive)
Returns:
- error: Error if any template name is invalid
func (*Sanitizer) MergeTemplates ¶
func (s *Sanitizer) MergeTemplates(names ...string) ([]SanitizePattern, error)
MergeTemplates combines patterns from multiple templates without modifying the sanitizer.
This method is useful for previewing what patterns would be loaded from multiple templates, or for creating custom template combinations.
The returned patterns are sorted by priority (highest first) and maintain insertion order for equal priorities.
Example:
sanitizer := devtools.NewSanitizer()
patterns, err := sanitizer.MergeTemplates("pii", "pci")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Combined templates have %d patterns\n", len(patterns))
Parameters:
- names: Template names to merge (case-sensitive)
Returns:
- []SanitizePattern: Combined patterns sorted by priority
- error: Error if any template name is invalid
func (*Sanitizer) PatternCount ¶
PatternCount returns the number of patterns configured.
Returns:
- int: Number of sanitization patterns
func (*Sanitizer) Preview ¶
func (s *Sanitizer) Preview(data *ExportData) *DryRunResult
Preview is a convenience method for dry-run sanitization.
This is equivalent to calling SanitizeWithOptions with DryRun: true. It returns a DryRunResult showing what would be redacted without actually modifying the data.
Example:
sanitizer := devtools.NewSanitizer()
result := sanitizer.Preview(exportData)
fmt.Printf("Would redact %d values\n", result.WouldRedactCount)
for _, match := range result.Matches {
fmt.Printf(" %s: %s → %s\n", match.Path, match.Original, match.Redacted)
}
Parameters:
- data: The export data to preview
Returns:
- *DryRunResult: Preview results showing what would be redacted
func (*Sanitizer) ResetStats ¶
func (s *Sanitizer) ResetStats()
ResetStats clears the stored statistics.
This is useful when you want to start fresh or free memory from old stats. After calling this, GetLastStats() will return nil until the next sanitization.
Thread Safety:
Safe to call concurrently. Uses write lock for thread-safe access.
Example:
sanitizer := NewSanitizer()
_ = sanitizer.SanitizeString(`{"password": "secret"}`)
sanitizer.ResetStats()
stats := sanitizer.GetLastStats() // Returns nil
func (*Sanitizer) Sanitize ¶
func (s *Sanitizer) Sanitize(data *ExportData) *ExportData
Sanitize creates a sanitized copy of the export data.
This method applies all configured patterns to the export data, recursively sanitizing nested structures. The original data is not modified - a deep copy is created and sanitized.
Statistics are tracked and can be retrieved with GetLastStats().
Thread Safety:
Safe to call concurrently. Does not modify the input data.
Example:
sanitizer := devtools.NewSanitizer()
cleanData := sanitizer.Sanitize(exportData)
stats := sanitizer.GetLastStats()
fmt.Printf("Redacted %d values in %v\n", stats.RedactedCount, stats.Duration)
Parameters:
- data: The export data to sanitize
Returns:
- *ExportData: A sanitized copy of the export data
func (*Sanitizer) SanitizeString ¶
SanitizeString applies all patterns to a single string in priority order.
This is a convenience method for sanitizing individual strings without needing to sanitize an entire ExportData structure.
Patterns are applied in priority order (highest first), with equal priorities maintaining insertion order (stable sort).
Statistics are tracked and can be retrieved with GetLastStats().
Example:
sanitizer := devtools.NewSanitizer()
clean := sanitizer.SanitizeString(`{"password": "secret123"}`)
// clean will be `{"password": "[REDACTED]"}`
stats := sanitizer.GetLastStats()
fmt.Printf("Redacted %d values\n", stats.RedactedCount)
Parameters:
- str: The string to sanitize
Returns:
- string: The sanitized string
func (*Sanitizer) SanitizeValue ¶
func (s *Sanitizer) SanitizeValue(val interface{}) interface{}
SanitizeValue recursively sanitizes a value of any type.
This method handles:
- Strings: applies all regex patterns in priority order
- Maps: recursively sanitizes all values
- Slices: recursively sanitizes all elements
- Structs: recursively sanitizes all exported fields
- Primitives: returns as-is (numbers, bools, etc.)
Patterns are applied in priority order (highest first), with equal priorities maintaining insertion order (stable sort).
The original value is not modified - a deep copy is created.
Thread Safety:
Safe to call concurrently. Does not modify the input value.
Example:
sanitizer := devtools.NewSanitizer()
cleanValue := sanitizer.SanitizeValue(map[string]interface{}{
"username": "alice",
"password": "secret123",
})
// cleanValue["password"] will be "[REDACTED]"
Parameters:
- val: The value to sanitize
Returns:
- interface{}: A sanitized copy of the value
func (*Sanitizer) SanitizeValueOptimized ¶
func (s *Sanitizer) SanitizeValueOptimized(val interface{}) interface{}
SanitizeValueOptimized is an optimized version of SanitizeValue that uses type caching.
This method provides 30-50% performance improvement over SanitizeValue for workloads that process many values of the same types (e.g., large exports with repeated structures).
The optimization works by caching reflection metadata (type kind, struct fields, element types) so repeated type introspection is avoided. The cache is thread-safe and uses sync.Map for lock-free concurrent access.
Performance Characteristics:
- First access to a type: Similar speed to SanitizeValue (cache miss)
- Subsequent accesses: 30-50% faster (cache hit)
- Memory overhead: ~100 bytes per cached type
- Cache hit rate: Typically >80% for real workloads
When to Use:
- Large data structures with repeated types
- Batch processing of similar structures
- Performance-critical sanitization paths
When to Use Standard SanitizeValue:
- Small, one-off sanitizations
- Many unique types with no repetition
- Memory-constrained environments
Thread Safety:
Safe to call concurrently. Both the type cache and pattern application are thread-safe.
Example:
sanitizer := devtools.NewSanitizer()
// Process thousands of similar structures efficiently
for _, record := range records {
clean := sanitizer.SanitizeValueOptimized(record)
// ... use clean record
}
// Check cache performance
size, hitRate := getTypeCacheStats()
fmt.Printf("Cache: %d types, %.1f%% hit rate\n", size, hitRate*100)
Parameters:
- val: The value to sanitize
Returns:
- interface{}: A sanitized copy of the value
func (*Sanitizer) SanitizeWithOptions ¶
func (s *Sanitizer) SanitizeWithOptions(data *ExportData, opts SanitizeOptions) (*ExportData, *DryRunResult)
SanitizeWithOptions sanitizes data with configurable options.
When DryRun is true, this method collects matches without modifying the original data and returns a DryRunResult. When DryRun is false, it performs normal sanitization and returns the sanitized data.
Thread Safety:
Safe to call concurrently. Does not modify input data in dry-run mode.
Example:
sanitizer := devtools.NewSanitizer()
opts := SanitizeOptions{
DryRun: true,
MaxPreviewLen: 100,
}
_, dryRunResult := sanitizer.SanitizeWithOptions(exportData, opts)
fmt.Printf("Would redact %d values\n", dryRunResult.WouldRedactCount)
Parameters:
- data: The export data to sanitize or preview
- opts: Options controlling sanitization behavior
Returns:
- *ExportData: Sanitized data (nil if DryRun is true)
- *DryRunResult: Dry-run results (nil if DryRun is false)
type SearchWidget ¶
type SearchWidget struct {
// contains filtered or unexported fields
}
SearchWidget provides fuzzy search functionality for component snapshots.
The search widget supports: - Case-insensitive substring matching on component names and types - Result navigation with keyboard controls - Thread-safe concurrent access - Performance optimized for large component trees
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
sw := devtools.NewSearchWidget(components)
sw.Search("button")
selected := sw.GetSelected()
output := sw.Render()
func NewSearchWidget ¶
func NewSearchWidget(components []*ComponentSnapshot) *SearchWidget
NewSearchWidget creates a new search widget with the given components.
The components parameter can be nil or empty, in which case searches will return no results until components are set via SetComponents.
func (*SearchWidget) Clear ¶
func (sw *SearchWidget) Clear()
Clear resets the search query, results, and cursor.
func (*SearchWidget) GetCursor ¶
func (sw *SearchWidget) GetCursor() int
GetCursor returns the current cursor position.
func (*SearchWidget) GetQuery ¶
func (sw *SearchWidget) GetQuery() string
GetQuery returns the current search query.
func (*SearchWidget) GetResultCount ¶
func (sw *SearchWidget) GetResultCount() int
GetResultCount returns the number of search results.
func (*SearchWidget) GetResults ¶
func (sw *SearchWidget) GetResults() []*ComponentSnapshot
GetResults returns a copy of the current search results.
func (*SearchWidget) GetSelected ¶
func (sw *SearchWidget) GetSelected() *ComponentSnapshot
GetSelected returns the currently selected result, or nil if there are no results.
func (*SearchWidget) NextResult ¶
func (sw *SearchWidget) NextResult()
NextResult moves the cursor to the next result, wrapping around to the first result if at the end.
func (*SearchWidget) PrevResult ¶
func (sw *SearchWidget) PrevResult()
PrevResult moves the cursor to the previous result, wrapping around to the last result if at the beginning.
func (*SearchWidget) Render ¶
func (sw *SearchWidget) Render() string
Render generates the visual representation of the search widget.
The output includes: - Search query input - Result count and current position - List of results with cursor indicator - Selected result highlighting
Returns a styled message if there are no results.
func (*SearchWidget) Search ¶
func (sw *SearchWidget) Search(query string)
Search performs a fuzzy search on component names and types.
The search is case-insensitive and matches substrings. An empty query returns all components. The cursor is reset to 0 after each search.
Matching criteria: - Component name contains query (case-insensitive) - Component type contains query (case-insensitive)
func (*SearchWidget) SetComponents ¶
func (sw *SearchWidget) SetComponents(components []*ComponentSnapshot)
SetComponents updates the component search space.
This does not automatically re-run the current search. Call Search() again to search the new components.
type StateChange ¶
type StateChange struct {
// ID is the auto-incrementing unique identifier for this state change
ID int64 `json:"id"`
// RefID is the unique identifier of the ref that changed
RefID string
// RefName is the variable name of the ref
RefName string
// OldValue is the value before the change
OldValue interface{}
// NewValue is the value after the change
NewValue interface{}
// Timestamp is when the change occurred
Timestamp time.Time
// Source identifies what caused the change (component ID, function name, etc.)
Source string
}
StateChange represents a single state mutation.
type StateHistory ¶
type StateHistory struct {
// contains filtered or unexported fields
}
StateHistory tracks changes to reactive state over time.
It maintains a circular buffer of state changes with a configurable maximum size. When the buffer is full, the oldest changes are discarded to make room for new ones.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
history := NewStateHistory(1000)
history.Record(StateChange{
RefID: "ref-1",
RefName: "counter",
OldValue: 41,
NewValue: 42,
Timestamp: time.Now(),
Source: "increment",
})
func NewStateHistory ¶
func NewStateHistory(maxSize int) *StateHistory
NewStateHistory creates a new state history with the specified maximum size.
The maxSize parameter determines how many state changes to keep in memory. When the limit is reached, the oldest changes are discarded.
Example:
history := NewStateHistory(1000) // Keep last 1000 changes
Parameters:
- maxSize: Maximum number of state changes to keep
Returns:
- *StateHistory: A new state history instance
func (*StateHistory) Clear ¶
func (sh *StateHistory) Clear()
Clear removes all state changes from the history.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*StateHistory) GetAll ¶
func (sh *StateHistory) GetAll() []StateChange
GetAll returns all state changes in the history.
The returned slice is a copy and safe to modify without affecting the internal state.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- []StateChange: All state changes
func (*StateHistory) GetHistory ¶
func (sh *StateHistory) GetHistory(refID string) []StateChange
GetHistory returns all state changes for a specific ref.
The returned slice is a copy and safe to modify without affecting the internal state.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
changes := history.GetHistory("ref-1")
for _, change := range changes {
fmt.Printf("%v -> %v\n", change.OldValue, change.NewValue)
}
Parameters:
- refID: The ref ID to get history for
Returns:
- []StateChange: All changes for the specified ref
func (*StateHistory) GetMaxID ¶
func (sh *StateHistory) GetMaxID() int64
GetMaxID returns the highest ID assigned to any state change.
This is used for creating checkpoints in incremental exports.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- int64: The highest ID, or 0 if no state changes exist
func (*StateHistory) Record ¶
func (sh *StateHistory) Record(change StateChange)
Record adds a state change to the history.
If the history is at maximum capacity, the oldest change is removed to make room for the new one. An auto-incrementing ID is assigned to the state change for incremental export tracking.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
history.Record(StateChange{
RefID: "ref-1",
RefName: "counter",
OldValue: 41,
NewValue: 42,
Timestamp: time.Now(),
Source: "increment",
})
Parameters:
- change: The state change to record
type StateHook ¶
type StateHook interface {
// OnRefChanged is called when a ref's value changes
OnRefChanged(refID string, oldValue, newValue interface{})
// OnComputedEvaluated is called when a computed value is evaluated
OnComputedEvaluated(computedID string, value interface{}, duration time.Duration)
// OnWatcherTriggered is called when a watcher is triggered
OnWatcherTriggered(watcherID string, value interface{})
}
StateHook is called when reactive state changes occur.
Implementations must be thread-safe as hooks can be called concurrently from multiple goroutines.
Example:
type MyStateHook struct{}
func (h *MyStateHook) OnRefChanged(refID string, oldValue, newValue interface{}) {
fmt.Printf("Ref %s changed: %v -> %v\n", refID, oldValue, newValue)
}
type StateViewer ¶
type StateViewer struct {
// contains filtered or unexported fields
}
StateViewer displays all reactive state in the application.
It provides a view of all refs across all components with filtering, selection, and value editing capabilities. The viewer integrates with the Store to access component snapshots and their refs.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
store := NewDevToolsStore(1000, 5000, 1000)
viewer := NewStateViewer(store)
viewer.SetFilter("count")
viewer.SelectRef("ref-1")
output := viewer.Render()
func NewStateViewer ¶
func NewStateViewer(store *Store) *StateViewer
NewStateViewer creates a new state viewer for the given store.
The viewer starts with no selection and no filter applied.
Example:
store := NewDevToolsStore(1000, 5000, 1000) viewer := NewStateViewer(store)
Parameters:
- store: The dev tools store to read state from
Returns:
- *StateViewer: A new state viewer instance
func (*StateViewer) ClearSelection ¶
func (sv *StateViewer) ClearSelection()
ClearSelection clears the current ref selection.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*StateViewer) EditValue ¶
func (sv *StateViewer) EditValue(id string, value interface{}) error
EditValue edits the value of a ref.
This method updates the ref value in the store. The ref must exist in one of the components, otherwise an error is returned.
Note: This is a development tool feature. In a real application, editing values directly may have side effects and should be used with caution.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
err := viewer.EditValue("ref-1", 100)
if err != nil {
fmt.Printf("Failed to edit: %v\n", err)
}
Parameters:
- id: The ref ID to edit
- value: The new value
Returns:
- error: An error if the ref was not found
func (*StateViewer) GetFilter ¶
func (sv *StateViewer) GetFilter() string
GetFilter returns the current filter string.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- string: The current filter
func (*StateViewer) GetSelected ¶
func (sv *StateViewer) GetSelected() *RefSnapshot
GetSelected returns the currently selected ref.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- *RefSnapshot: The selected ref, or nil if no ref is selected
func (*StateViewer) Render ¶
func (sv *StateViewer) Render() string
Render generates the visual output of the state viewer.
The output includes:
- A header with the title "Reactive State"
- Component sections with their refs
- Selection indicator (►) for the selected ref
- Filtered refs based on the current filter
- Styled with Lipgloss for terminal display
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- string: The rendered output
func (*StateViewer) SelectRef ¶
func (sv *StateViewer) SelectRef(id string) bool
SelectRef selects a ref by ID.
If the ref exists in any component, it is selected and the method returns true. If the ref does not exist, the selection is cleared and the method returns false.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
if viewer.SelectRef("ref-1") {
fmt.Println("Ref selected")
}
Parameters:
- id: The ref ID to select
Returns:
- bool: True if the ref was found and selected, false otherwise
func (*StateViewer) SetFilter ¶
func (sv *StateViewer) SetFilter(filter string)
SetFilter sets the filter string for ref names.
The filter is case-insensitive and matches substrings. An empty string clears the filter.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
viewer.SetFilter("count") // Show only refs with "count" in name
viewer.SetFilter("") // Show all refs
Parameters:
- filter: The filter string
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store holds all collected debug data in memory.
It provides thread-safe storage for component snapshots, state history, events, performance metrics, and command timeline. The store acts as the central data repository for the dev tools system.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
store := NewDevToolsStore(1000, 5000, 1000)
// Add component
snapshot := &ComponentSnapshot{ID: "comp-1", Name: "Counter"}
store.AddComponent(snapshot)
// Get component
comp := store.GetComponent("comp-1")
func NewDevToolsStore ¶
NewDevToolsStore creates a new dev tools store with the specified limits.
Parameters:
- maxStateHistory: Maximum number of state changes to keep
- maxEvents: Maximum number of events to keep
- maxCommands: Maximum number of commands to keep
Returns:
- *Store: A new store instance
func (*Store) AddComponent ¶
func (s *Store) AddComponent(snapshot *ComponentSnapshot)
AddComponent adds or updates a component snapshot in the store.
If a component with the same ID already exists, it is replaced.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
snapshot := &ComponentSnapshot{
ID: "comp-1",
Name: "Counter",
}
store.AddComponent(snapshot)
Parameters:
- snapshot: The component snapshot to add
func (*Store) AddComponentChild ¶
AddComponentChild adds a child-parent relationship in the component tree.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- parentID: The parent component ID
- childID: The child component ID
func (*Store) Clear ¶
func (s *Store) Clear()
Clear removes all data from the store.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*Store) GetAllComponents ¶
func (s *Store) GetAllComponents() []*ComponentSnapshot
GetAllComponents returns all component snapshots.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- []*ComponentSnapshot: All component snapshots
func (*Store) GetComponent ¶
func (s *Store) GetComponent(id string) *ComponentSnapshot
GetComponent retrieves a component snapshot by ID.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- id: The component ID
Returns:
- *ComponentSnapshot: The component snapshot, or nil if not found
func (*Store) GetComponentChildren ¶
GetComponentChildren returns the IDs of a component's direct children.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- componentID: The parent component ID
Returns:
- []string: List of child component IDs
func (*Store) GetEventLog ¶
GetEventLog returns the event log.
Returns:
- *EventLog: The event log instance
func (*Store) GetPerformanceData ¶
func (s *Store) GetPerformanceData() *PerformanceData
GetPerformanceData returns the performance data tracker.
Returns:
- *PerformanceData: The performance data instance
func (*Store) GetRootComponents ¶
func (s *Store) GetRootComponents() []*ComponentSnapshot
GetRootComponents returns components that have no parent.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- []*ComponentSnapshot: List of root components
func (*Store) GetSince ¶
func (s *Store) GetSince(checkpoint *ExportCheckpoint) (*IncrementalExportData, error)
GetSince returns incremental data since the given checkpoint.
This method filters events, state changes, and commands to include only those with IDs greater than the checkpoint's last IDs. This enables incremental exports that contain only new data.
Thread Safety:
Safe to call concurrently. Uses read locks on store.
Example:
checkpoint := &ExportCheckpoint{
LastEventID: 100,
LastStateID: 50,
LastCommandID: 25,
}
delta, err := store.GetSince(checkpoint)
if err != nil {
log.Printf("Failed to get delta: %v", err)
}
Parameters:
- checkpoint: The checkpoint to filter from
Returns:
- *IncrementalExportData: The filtered incremental data
- error: nil on success, error describing the failure otherwise
func (*Store) GetStateHistory ¶
func (s *Store) GetStateHistory() *StateHistory
GetStateHistory returns the state history tracker.
Returns:
- *StateHistory: The state history instance
func (*Store) RegisterRefOwner ¶
RegisterRefOwner registers that a specific component owns a specific ref. This enables proper tracking of which refs belong to which components.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- componentID: The component that owns the ref
- refID: The ref ID being owned
func (*Store) RemoveComponent ¶
RemoveComponent removes a component snapshot from the store.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- id: The component ID to remove
func (*Store) RemoveComponentChild ¶
RemoveComponentChild removes a child-parent relationship from the component tree.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- parentID: The parent component ID
- childID: The child component ID to remove
func (*Store) UpdateRefValue ¶
UpdateRefValue updates a ref value only for the component that owns it. Returns the owning component ID, or empty string if ref has no owner.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Parameters:
- refID: The ref to update
- newValue: The new value
Returns:
- string: The owning component ID
- bool: Whether the update was applied
type StreamSanitizer ¶
type StreamSanitizer struct {
// Sanitizer is the base sanitizer with patterns
*Sanitizer
// contains filtered or unexported fields
}
StreamSanitizer provides streaming sanitization for large export files.
It processes JSON data in a streaming fashion using json.Decoder and bufio.Writer, ensuring memory usage stays bounded regardless of input size. This is essential for handling exports >100MB without causing out-of-memory errors.
Memory Guarantees:
- Memory usage is O(buffer size), not O(input size)
- Processes data component-by-component
- Suitable for files >100MB
Thread Safety:
Safe to use concurrently. Each SanitizeStream call operates independently.
Example:
base := devtools.NewSanitizer()
stream := devtools.NewStreamSanitizer(base, 64*1024) // 64KB buffer
file, _ := os.Open("large-export.json")
defer file.Close()
out, _ := os.Create("sanitized-export.json")
defer out.Close()
progress := func(bytesProcessed int64) {
fmt.Printf("Processed: %d bytes\n", bytesProcessed)
}
err := stream.SanitizeStream(file, out, progress)
func NewStreamSanitizer ¶
func NewStreamSanitizer(base *Sanitizer, bufferSize int) *StreamSanitizer
NewStreamSanitizer creates a new streaming sanitizer.
The buffer size determines the memory usage for buffered I/O operations. Larger buffers can improve performance but use more memory. The default is 64KB if bufferSize is 0 or negative.
Recommended buffer sizes:
- 4KB: Minimal memory usage, slower
- 64KB: Balanced (default)
- 1MB: High performance, more memory
Example:
base := devtools.NewSanitizer() stream := devtools.NewStreamSanitizer(base, 64*1024) // 64KB buffer
Parameters:
- base: The base sanitizer with configured patterns
- bufferSize: Size of I/O buffer in bytes (0 for default 64KB)
Returns:
- *StreamSanitizer: New streaming sanitizer instance
func (*StreamSanitizer) SanitizeStream ¶
func (s *StreamSanitizer) SanitizeStream(reader io.Reader, writer io.Writer, progress func(bytesProcessed int64)) error
SanitizeStream performs streaming sanitization from reader to writer.
This method processes JSON data in a streaming fashion, ensuring bounded memory usage regardless of input size. It reads the input as a string, applies sanitization patterns, and writes the sanitized output.
The progress callback is invoked periodically (every ~64KB processed) to report the number of bytes processed. This is useful for long-running operations to provide user feedback.
Memory Usage:
- Bounded by buffer size (typically 64KB)
- Processes data in chunks
- Suitable for files >100MB
Performance:
- Target: <10% slower than in-memory processing
- Constant memory usage
- Efficient for large files
Thread Safety:
Safe to call concurrently. Each call operates independently.
Example:
stream := devtools.NewStreamSanitizer(base, 64*1024)
file, _ := os.Open("export.json")
defer file.Close()
out, _ := os.Create("sanitized.json")
defer out.Close()
progress := func(bytes int64) {
fmt.Printf("Progress: %d bytes\n", bytes)
}
err := stream.SanitizeStream(file, out, progress)
if err != nil {
log.Fatal(err)
}
Parameters:
- reader: Input stream containing JSON data
- writer: Output stream for sanitized JSON data
- progress: Optional callback for progress reporting (can be nil)
Returns:
- error: nil on success, error describing the failure otherwise
type Tab ¶
type Tab struct {
Name string
Render func(*ComponentSnapshot) string
}
Tab represents a single tab in the detail panel.
Each tab has a name and a render function that generates the content for that tab based on the current component.
type TabController ¶
type TabController struct {
// contains filtered or unexported fields
}
TabController manages tab navigation and rendering in dev tools.
The tab controller supports: - Multiple tabs with custom content - Keyboard navigation (Next, Prev, Select) - Thread-safe concurrent access - Visual highlighting of active tab
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
tabs := []TabItem{
{Name: "Inspector", Content: func() string { return "Inspector content" }},
{Name: "State", Content: func() string { return "State content" }},
{Name: "Events", Content: func() string { return "Events content" }},
}
tc := devtools.NewTabController(tabs)
tc.Next() // Switch to next tab
output := tc.Render()
func NewTabController ¶
func NewTabController(tabs []TabItem) *TabController
NewTabController creates a new tab controller with the given tabs.
The first tab (index 0) is active by default. If no tabs are provided, the controller will render a "No tabs" message.
func (*TabController) GetActiveTab ¶
func (tc *TabController) GetActiveTab() int
GetActiveTab returns the index of the currently active tab.
func (*TabController) Next ¶
func (tc *TabController) Next()
Next switches to the next tab, wrapping around to the first tab if currently on the last tab.
If there are no tabs or only one tab, this is a no-op.
func (*TabController) Prev ¶
func (tc *TabController) Prev()
Prev switches to the previous tab, wrapping around to the last tab if currently on the first tab.
If there are no tabs or only one tab, this is a no-op.
func (*TabController) Render ¶
func (tc *TabController) Render() string
Render generates the visual representation of the tab controller.
The output includes: - Tab navigation bar with active tab highlighted - Active tab content
Returns a styled message if no tabs are configured.
func (*TabController) Select ¶
func (tc *TabController) Select(index int)
Select switches to the tab at the given index.
If the index is out of bounds, the active tab remains unchanged.
type TabItem ¶
TabItem represents a single tab in the tab controller.
Each tab has a name and a content function that generates the content for that tab.
type TemplateRegistry ¶
type TemplateRegistry map[string][]SanitizePattern
TemplateRegistry is a map of template names to their sanitization patterns.
Templates provide pre-configured pattern sets for common compliance requirements like PII, PCI, HIPAA, and GDPR. Each template contains patterns with appropriate priorities for the compliance domain.
Example:
registry := devtools.DefaultTemplates
piiPatterns := registry["pii"]
fmt.Printf("PII template has %d patterns\n", len(piiPatterns))
var ( // DefaultTemplates contains pre-configured compliance pattern sets. // // Available templates: // - "pii": Personal Identifiable Information (SSN, email, phone) // - "pci": Payment Card Industry (card numbers, CVV, expiry dates) // - "hipaa": Health Insurance Portability and Accountability Act (MRN, diagnoses) // - "gdpr": General Data Protection Regulation (IP addresses, MAC addresses) // // All patterns use case-insensitive matching with (?i) flag and capture // groups to preserve keys while redacting values: (key)(sep)(value) // // Example: // // sanitizer := devtools.NewSanitizer() // sanitizer.LoadTemplate("pii") // Loads SSN, email, phone patterns // sanitizer.LoadTemplate("pci") // Adds card, CVV, expiry patterns DefaultTemplates TemplateRegistry )
type TimeRange ¶
type TimeRange struct {
// Start is the start time (inclusive)
Start time.Time
// End is the end time (inclusive)
End time.Time
}
TimeRange represents a time range for filtering events.
type TimelineControls ¶
type TimelineControls struct {
// contains filtered or unexported fields
}
TimelineControls provides scrubbing and replay functionality for command timelines.
It allows developers to navigate through command history, replay commands at different speeds, and pause/resume playback. This is useful for debugging asynchronous behavior and understanding command execution flow.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
timeline := NewCommandTimeline(1000) controls := NewTimelineControls(timeline) controls.SetSpeed(2.0) // 2x speed cmd := controls.Replay() // Use cmd in Bubbletea Update() method
func NewTimelineControls ¶
func NewTimelineControls(timeline *CommandTimeline) *TimelineControls
NewTimelineControls creates a new timeline controls instance.
The controls start at position 0, speed 1.0, not replaying, and not paused.
Example:
timeline := NewCommandTimeline(1000) controls := NewTimelineControls(timeline)
Parameters:
- timeline: The command timeline to control
Returns:
- *TimelineControls: A new timeline controls instance
func (*TimelineControls) GetPosition ¶
func (tc *TimelineControls) GetPosition() int
GetPosition returns the current timeline position.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- int: The current position (0-indexed)
func (*TimelineControls) GetSpeed ¶
func (tc *TimelineControls) GetSpeed() float64
GetSpeed returns the current replay speed multiplier.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- float64: The current speed multiplier
func (*TimelineControls) IsPaused ¶
func (tc *TimelineControls) IsPaused() bool
IsPaused returns whether the replay is currently paused.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- bool: True if paused, false otherwise
func (*TimelineControls) IsReplaying ¶
func (tc *TimelineControls) IsReplaying() bool
IsReplaying returns whether replay is currently active.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- bool: True if replaying, false otherwise
func (*TimelineControls) Pause ¶
func (tc *TimelineControls) Pause()
Pause pauses the replay.
Commands already in flight will complete, but no new commands will be emitted until Resume is called.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
controls.Pause()
func (*TimelineControls) Render ¶
func (tc *TimelineControls) Render(width int) string
func (*TimelineControls) Replay ¶
func (tc *TimelineControls) Replay() tea.Cmd
Replay starts replaying commands and returns a Bubbletea command.
The command emits ReplayCommandMsg for each command with appropriate timing based on the original command timestamps and the current speed setting.
If the timeline is empty, returns nil immediately. If already replaying, returns nil.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
cmd := controls.Replay()
if cmd != nil {
return m, cmd
}
Returns:
- tea.Cmd: The command to start replay, or nil if no commands
func (*TimelineControls) Resume ¶
func (tc *TimelineControls) Resume()
Resume resumes the replay.
After calling Resume, replay will continue from the current position.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
controls.Resume()
func (*TimelineControls) Scrub ¶
func (tc *TimelineControls) Scrub(position int)
Scrub sets the timeline position to the specified index.
The position is clamped to valid range [0, commandCount-1]. If the timeline is empty, position is set to 0.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
controls.Scrub(5) // Jump to command 5
Parameters:
- position: The target position (0-indexed)
func (*TimelineControls) ScrubBackward ¶
func (tc *TimelineControls) ScrubBackward()
ScrubBackward moves the position backward by one command.
If already at the start, position remains unchanged.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
controls.ScrubBackward() // Move to previous command
func (*TimelineControls) ScrubForward ¶
func (tc *TimelineControls) ScrubForward()
ScrubForward moves the position forward by one command.
If already at the end, position remains unchanged.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
controls.ScrubForward() // Move to next command
func (*TimelineControls) SetSpeed ¶
func (tc *TimelineControls) SetSpeed(speed float64) error
SetSpeed sets the replay speed multiplier.
Valid range is 0.1 to 10.0. Values outside this range return an error. Speed of 1.0 is normal speed, 2.0 is double speed, 0.5 is half speed.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
err := controls.SetSpeed(2.0) // 2x speed
if err != nil {
// Handle error
}
Parameters:
- speed: The speed multiplier (0.1 to 10.0)
Returns:
- error: Error if speed is out of valid range
type TreeView ¶
type TreeView struct {
// contains filtered or unexported fields
}
TreeView displays a hierarchical tree of component snapshots.
The tree view supports: - Expanding/collapsing nodes to show/hide children - Selecting components for detailed inspection - Keyboard navigation (up/down) - Visual indicators for selection and expansion state
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
tv := devtools.NewTreeView(rootSnapshot)
tv.Expand("component-1")
tv.Select("component-2")
output := tv.Render()
func NewTreeView ¶
func NewTreeView(root *ComponentSnapshot) *TreeView
NewTreeView creates a new tree view with the given root component.
The root component can be nil, in which case the tree view will display an empty state message.
func (*TreeView) GetExpandedIDs ¶
GetExpandedIDs returns a copy of all currently expanded node IDs. This is used to preserve expansion state when rebuilding the tree.
func (*TreeView) GetRoot ¶
func (tv *TreeView) GetRoot() *ComponentSnapshot
GetRoot returns the root component snapshot.
func (*TreeView) GetSelected ¶
func (tv *TreeView) GetSelected() *ComponentSnapshot
GetSelected returns the currently selected component snapshot.
func (*TreeView) IsExpanded ¶
IsExpanded returns whether a component node is expanded.
func (*TreeView) Render ¶
Render generates the visual representation of the component tree.
The output includes: - Tree structure with indentation - Expand/collapse indicators (▶/▼) - Selection indicator (►) - Component names and ref counts
Returns an empty message if the tree has no root component.
func (*TreeView) Select ¶
Select sets the selected component by ID.
If the component with the given ID is not found in the tree, the selection remains unchanged.
func (*TreeView) SelectNext ¶
func (tv *TreeView) SelectNext()
SelectNext selects the next visible component in the tree.
Navigation follows depth-first traversal order, respecting the current expansion state of nodes.
func (*TreeView) SelectPrevious ¶
func (tv *TreeView) SelectPrevious()
SelectPrevious selects the previous visible component in the tree.
func (*TreeView) SetExpandedIDs ¶
SetExpandedIDs sets which nodes should be expanded. This is used to restore expansion state after rebuilding the tree.
type UI ¶
type UI struct {
// contains filtered or unexported fields
}
UI is the main UI component that integrates all dev tools panels.
It manages: - Layout (split-pane, overlay, etc.) - Tab navigation between panels - Keyboard shortcuts - Panel rendering (inspector, state, events, performance, timeline)
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
store := devtools.NewDevToolsStore(1000, 1000, 1000)
ui := devtools.NewDevToolsUI(store)
ui.SetAppContent("My Application")
// In Bubbletea Update()
updatedUI, cmd := ui.Update(msg)
// In Bubbletea View()
output := ui.View()
func NewDevToolsUI ¶
NewDevToolsUI creates a new DevTools UI with all panels initialized.
The UI starts with the component inspector panel active and a horizontal split layout with a 60/40 ratio (app 60%, tools 40%).
Example:
store := devtools.NewDevToolsStore(1000, 1000, 1000) ui := devtools.NewDevToolsUI(store)
Parameters:
- store: The dev tools data store
Returns:
- *UI: A new DevTools UI instance
func (*UI) EnableAutoLayout ¶
func (ui *UI) EnableAutoLayout()
EnableAutoLayout re-enables automatic responsive layout adjustments.
After calling this, the UI will automatically adjust layout mode and ratio based on terminal size according to the responsive breakpoints:
- < 80 cols: Vertical layout
- 80-120 cols: Horizontal 50/50
- > 120 cols: Horizontal 40/60
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
ui.SetManualLayoutMode(LayoutOverlay) // Disable auto layout // ... later ... ui.EnableAutoLayout() // Re-enable auto layout
func (*UI) GetActivePanel ¶
GetActivePanel returns the index of the currently active panel.
Panel indices:
- 0: Component Inspector
- 1: State Viewer
- 2: Event Tracker
- 3: Performance Monitor
- 4: Command Timeline
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- int: The active panel index
func (*UI) GetLayoutMode ¶
func (ui *UI) GetLayoutMode() LayoutMode
GetLayoutMode returns the current layout mode.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- LayoutMode: The current layout mode
func (*UI) GetLayoutRatio ¶
GetLayoutRatio returns the current split ratio.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Returns:
- float64: The current split ratio (0.0-1.0)
func (*UI) Init ¶
Init initializes the DevTools UI.
This method is required by the tea.Model interface but currently does nothing as all initialization is done in NewDevToolsUI.
Returns:
- tea.Cmd: Always returns nil
func (*UI) IsFocusMode ¶
IsFocusMode returns whether DevTools is in focus mode.
When in focus mode, keyboard input is routed to DevTools for navigation. When not in focus mode, keyboard input goes to the application.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*UI) SetActivePanel ¶
SetActivePanel sets the active panel by index.
If the index is out of bounds, the active panel is not changed.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
ui.SetActivePanel(1) // Switch to State Viewer
Parameters:
- index: The panel index (0-4)
func (*UI) SetAppContent ¶
SetAppContent sets the application content to display in the layout.
The app content is shown alongside the dev tools panels according to the configured layout mode.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
ui.SetAppContent("My Application\nCounter: 42")
Parameters:
- content: The application content to display
func (*UI) SetFocusMode ¶
SetFocusMode sets the focus mode state.
This is useful for programmatically entering/exiting focus mode.
Thread Safety:
Safe to call concurrently from multiple goroutines.
func (*UI) SetLayoutMode ¶
func (ui *UI) SetLayoutMode(mode LayoutMode)
SetLayoutMode sets the layout mode for the UI.
Available modes:
- LayoutHorizontal: Side-by-side split
- LayoutVertical: Top/bottom split
- LayoutOverlay: Tools overlay on app
- LayoutHidden: Tools hidden
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
ui.SetLayoutMode(LayoutVertical)
Parameters:
- mode: The layout mode to set
func (*UI) SetLayoutRatio ¶
SetLayoutRatio sets the split ratio for the layout.
The ratio determines how much space the app gets vs the tools. Valid range is 0.0-1.0, where 0.6 means 60% app, 40% tools.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
ui.SetLayoutRatio(0.7) // 70% app, 30% tools
Parameters:
- ratio: The split ratio (0.0-1.0)
func (*UI) SetManualLayoutMode ¶
func (ui *UI) SetManualLayoutMode(mode LayoutMode)
SetManualLayoutMode sets the layout mode manually and disables automatic responsive layout.
When you manually set a layout mode, the UI will no longer automatically adjust the layout based on terminal size. Call EnableAutoLayout() to re-enable automatic responsive behavior.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
ui.SetManualLayoutMode(LayoutOverlay) // Force overlay mode // Terminal resizes will NOT change layout mode
Parameters:
- mode: The layout mode to set manually
func (*UI) View ¶
View renders the DevTools UI.
It combines the application content with the active panel's content using the configured layout manager.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
func (m model) View() string {
return m.ui.View()
}
Returns:
- string: The rendered UI
type VersionMigration ¶
type VersionMigration interface {
// From returns the source version this migration applies to
From() string
// To returns the target version this migration produces
To() string
// Migrate transforms data from source to target version.
// The input map should not be modified; return a new map or modified copy.
Migrate(data map[string]interface{}) (map[string]interface{}, error)
}
VersionMigration defines the interface for migrating data between versions.
Implementations should be idempotent and preserve all existing data while adding new fields or transforming structures as needed.
Thread Safety:
Implementations must be safe to call concurrently.
Example:
type Migration_1_0_to_2_0 struct{}
func (m *Migration_1_0_to_2_0) From() string { return "1.0" }
func (m *Migration_1_0_to_2_0) To() string { return "2.0" }
func (m *Migration_1_0_to_2_0) Migrate(data map[string]interface{}) (map[string]interface{}, error) {
// Add metadata field
data["metadata"] = map[string]interface{}{
"migrated_from": "1.0",
}
return data, nil
}
func GetMigrationPath ¶
func GetMigrationPath(from, to string) ([]VersionMigration, error)
GetMigrationPath finds a chain of migrations from source to target version.
Uses breadth-first search to find the shortest migration path. Returns an error if no path exists between the versions.
Thread Safety:
Safe to call concurrently.
Example:
path, err := GetMigrationPath("1.0", "2.0")
if err != nil {
log.Printf("No migration path: %v", err)
}
// path contains ordered list of migrations to apply
type YAMLFormat ¶
type YAMLFormat struct{}
YAMLFormat implements ExportFormat for YAML.
Uses github.com/goccy/go-yaml for marshaling and unmarshaling. This library provides better performance and features than gopkg.in/yaml.v3.
Thread Safety:
Safe for concurrent use.
func (*YAMLFormat) ContentType ¶
func (f *YAMLFormat) ContentType() string
ContentType returns "application/x-yaml".
func (*YAMLFormat) Marshal ¶
func (f *YAMLFormat) Marshal(data *ExportData) ([]byte, error)
Marshal serializes ExportData to YAML.
Parameters:
- data: The ExportData to marshal
Returns:
- []byte: YAML bytes
- error: nil on success, error on marshal failure
func (*YAMLFormat) Unmarshal ¶
func (f *YAMLFormat) Unmarshal(b []byte, data *ExportData) error
Unmarshal deserializes YAML bytes to ExportData.
Parameters:
- b: YAML bytes to unmarshal
- data: Pointer to ExportData to populate
Returns:
- error: nil on success, error on unmarshal failure
Source Files
¶
- collector.go
- command_timeline.go
- config.go
- detail_panel.go
- devtools.go
- doc.go
- event_filter.go
- event_replay.go
- event_tracker.go
- export.go
- filter.go
- flame_graph.go
- formats.go
- import.go
- incremental.go
- inspector.go
- instrumentation.go
- keyboard.go
- layout.go
- metrics.go
- migration.go
- optimize.go
- performance.go
- preview.go
- router_debugger.go
- sanitize.go
- sanitize_stream.go
- search.go
- snapshot.go
- state_viewer.go
- store.go
- tabs.go
- templates.go
- timeline_controls.go
- tree_view.go
- ui.go
- ui_constants.go