Appearance
Color Management
I admit it, my early code was a nightmare to tweak because i designed it as i went along. With AI, i crafted a centralized system. One place to confine the mess. Styles computes all colors from state snapshots. Remarkably simple code.
Table of Contents
- Overview
- Known Issues / Future Work
- Core Components
- States
- Driver
- Color Properties
- Architecture
- Define color schema per component/state
- Centralize color computation logic
- Standardize state transitions
- Document color inheritance rules
- Implementation Notes
- Styles manager (Implementation)
- Analyze set_forHovering
Overview
Centralized color management system driven by the Hits manager and hit targets. Colors are computed based on component type, state (hover/selected/none), and interaction context.
Known Issues / Future Work
- Glow buttons have some click handling issues (separate from color management)
- Consider adding tests for Styles computation functions
- Could further simplify S_Element helper getters if needed
- Centralize
set_forHoveringcalls - See "Analyze set_forHovering" section above
Core Components
- widget - Main widget container
- drag - Drag handle dot
- reveal - Reveal/expand dot
- button - UI buttons
States
- hover - Mouse is over the element
- selected - grab
- editing -
- focus -
- mouse down -
- none - Default/base state
Driver
- Hits Manager (
Hits.ts) - Tracks hover state viaw_s_hoverstore - Hit Target (
S_Hit_Target) - Base class for all interactive elements, provides hover state
Color Properties
Per Component
fill- Background/fill colorstroke- Border/text colorsvg_outline_color- SVG outline colorborder- CSS border style
State Logic
- Hover state from
hits.w_s_hover - Selected state from
isSelected - Inversion logic via
isInvertedandcolor_isInverted
Architecture
Current Implementation ✅
- Colors computed centrally in
Stylesutility class S_ElementandS_Widgetgetters callStylesmethods- Components remain reactive via getter pattern
set_forHovering()sets hover color and cursor (unchanged)
Implementation Status
- [x] Define color schema per component/state
- [x] Centralize color computation logic
- [x] Create
S_Snapshotstate object class - [x] Migrate widget, drag, reveal, button components
- [x] Standardize state transitions
- [x] Document color inheritance rules
- [ ] Analyze set_forHovering
Define color schema per component/state
Common Fill/Stroke Pattern (Drag, Reveal, Button)
Base setup:
element_color- Set viaset_forHovering(thing.color, cursor)hoverColor- Computed viacolors.hover_special_blend(element_color)color_background- Fromw_background_color
Fill/Stroke computation (all states except selected/disabled):
fill:color_isInverted ? hoverColor : color_backgroundstroke:color_isInverted ? color_background : element_color
Dots (Drag, Reveal)
Properties:
fill,stroke- Use common pattern abovesvg_outline_color- SVG path outline
States:
- none/hover:
svg_outline_color=thing_color - grabbed:
svg_outline_color=color_background - editing:
svg_outline_color=black(if light thing_color) orhoverColor(if dark)
Special:
- Drag:
parents_go=stroke,related_color=thing.color - Reveal:
bulkAlias_go=stroke
Widget
Properties:
color- Text/stroke (viacolorFor_grabbed_andEditing())background_color- Background fillborder- CSS border style
States:
- none:
color=thing_color,background_color=transparent,border=transparent - hover:
background_color=w_background_color,border=solid ${colors.ofBackgroundFor(thing_color)} 1px - grabbed:
color=black/white(based on thing_color luminance),background_color=thing_color - editing:
background_color=w_background_color,border=dashed ${thing_color} 1px - focus (radial):
background_color=w_background_color,border=solid ${thing_color} 1px
Button
Properties:
fill,stroke- Use common pattern aboveborder- CSS border style
Special states:
- selected:
fill=lightblue - disabled:
fill=transparent,stroke=disabledTextColor - editing/focus:
borderusesthing_color(widget context only)
Centralize color computation logic
Design Summary
Current State:
- Color logic scattered across
S_Element,S_Widgetgetters - Components call
update_colors()reactively with local variables - Duplication of fill/stroke inversion logic
- State combinations computed inline
Implemented Architecture:
Created Styles utility class (src/lib/ts/utilities/Styles.ts) with static methods:
- Takes inputs:
S_Snapshotstate object, base colors - Returns computed color values based on schema
- Encapsulates all conditional logic in one place
- Supports all component types (widget, drag, reveal, button)
Implementation Details:
Single computation function per component type ✅
Styles.computeWidgetColors(state, thing_color, background_color)→{ color, background_color, border }Styles.computeDotColors(state, element_color, thing_color, background_color, hoverColor)→{ fill, stroke, svg_outline_color }Styles.computeButtonColors(state, element_color, background_color, hoverColor, disabledTextColor, border_thickness, has_widget_context, thing_color)→{ fill, stroke, border }
State object pattern ✅
S_Snapshotclass (src/lib/ts/state/S_Snapshot.ts) - "state of go" - transient state of user attention/intention- Encapsulates all state flags needed for color computation
- Constructor accepts
S_Hit_Target | Identifiable | undefinedplus optional flags - Getters provide:
hover,grabbed,editing,focus,thing_color - Special hover logic: for widgets, also checks if title with same ancestry is hovered
Getters are thin wrappers ✅
S_Element/S_Widgetgetters createS_Snapshotinstance and callStylesmethods- Components remain reactive via getter pattern
- Logic centralized in
Styles, state collected inS_Snapshot
Preserve reactive behavior ✅
- Getters still reactive - Svelte tracks dependencies
- No changes to component reactive blocks needed
- All color logic testable in isolation
Benefits:
- Single source of truth for color computation
- Easier to modify color behavior globally
- Testable in isolation
- Reduces code duplication
- Clearer separation of concerns
Impact on State Objects
S_Hit_Target (base class):
- Keep:
hoverColor,element_color,set_forHovering()- these provide inputs to Styles - Keep:
isHoveringgetter - needed for state collection - No change: Hit testing and cursor logic remain here
S_Element (Implemented):
- Kept as inputs:
color_background,isDisabled,isSelected,isInverted,subtype - Getters now call Styles:
fill,stroke,svg_outline_color,bordercreateS_Snapshotand callStyles.computeDotColors()orStyles.computeButtonColors() - Helper getters:
s_go,thing_color,dotColors_forElement,buttonColors_forElementreduce duplication - Removed: Old inline color logic from getters
- Actual implementation:typescript
get fill(): string { if (this.asTransparent) { return 'transparent'; } else if (this.isADot) { return this.dotColors_forElement.fill; } else if (this.isAControl) { return this.buttonColors_forElement.fill; } else { return this.color_isInverted ? this.hoverColor : this.isSelected ? 'lightblue' : this.color_background; } }
S_Widget (Implemented):
- Getters now call Styles:
color,background_color,bordercreateS_Snapshotand callStyles.computeWidgetColors() - Removed:
colorFor_grabbed_andEditing(),isFilled,shows_border,isRadial_focushelper methods - Actual implementation:typescript
get color(): string { const state = new S_Snapshot(this, this.isDisabled, this.isSelected, this.isInverted, this.subtype); return Styles.computeWidgetColors(state, this.thing_color, get(colors.w_background_color)).color; }
State Collection Pattern:
- Each getter collects relevant state from
this,this.ancestry, and stores - Passes complete
S_Snapshotstate object to Styles - Styles handles all conditional logic
- State objects become data containers + thin computation wrappers
S_Snapshot (Implemented):
The S_Snapshot class (src/lib/ts/state/S_Snapshot.ts) - "state of go for style" - represents the transient state of user attention/intention. It's a snapshot of flags and relevances needed for style (for now, color) computation:
typescript
export default class S_Snapshot {
hit_target?: S_Hit_Target; // Hit target for hover detection (includes type + identifiable)
identifiable?: Identifiable; // Provides: isGrabbed, isEditing, isFocus, thing.color
isInverted?: boolean;
isDisabled?: boolean;
isSelected?: boolean;
subtype?: string; // For special cases (e.g., T_Control.details)
constructor(hit_target: S_Hit_Target | Identifiable | undefined,
isDisabled?: boolean,
isSelected?: boolean,
isInverted?: boolean,
subtype?: string)
// Getters:
get hover(): boolean // Checks hit_target.isHovering or compares identifiable.id with hits.w_s_hover
get grabbed(): boolean // Returns ancestry?.isGrabbed ?? false
get editing(): boolean // Returns ancestry?.isEditing ?? false
get focus(): boolean // Returns ancestry?.isFocus ?? false
get thing_color(): string // Returns ancestry?.thing?.color ?? k.empty
}Key characteristics:
- "State of go" - Represents transient user attention/intention state (hover, grab, edit, focus)
- Constructor accepts
S_Hit_TargetorIdentifiable- handles both patterns hovergetter has special logic: for widgets, checks if title with same ancestry is hovered (fixes widget title hover bug)grabbed,editing,focus,thing_colorsafely accessed viaancestrygetter (castsidentifiabletoAncestry)- State object is created fresh on each getter call (no caching needed due to Svelte reactivity)
- All properties are optional except those provided by getters
Standardize state transitions
Document color inheritance rules
Color Sources
Colors are sourced from different places depending on the color type and component:
| Color Type | Source | Used By | Notes |
|---|---|---|---|
thing_color | ancestry.thing?.color | Widget, Drag, Reveal | Inherited from the Thing object associated with the ancestry |
element_color | Set via set_forHovering(color, cursor) | Drag, Reveal, Button | Set explicitly per component, typically matches thing.color |
background_color | colors.w_background_color store | All components | Global background color, shared across entire UI |
hoverColor | colors.hover_special_blend(element_color) | Drag, Reveal, Button | Computed from element_color when hover state detected |
Inheritance Patterns
Thing Color Inheritance:
- Widgets, drag dots, and reveal dots inherit their base color from
ancestry.thing.color - Accessed via
S_Snapshot.thing_colorgetter - If no thing exists, defaults to
k.empty(empty string) - This is the primary color identity for a widget/item
Element Color Pattern:
- Drag and reveal components call
set_forHovering(thing.color, cursor)to setelement_color - Buttons use their own
element_color(may differ from thing color) element_coloris the base stroke/outline color when not hovered- Set once per component instance (not reactive to thing color changes)
Hover Color Computation:
hoverColoris computed fromelement_colorviacolors.hover_special_blend()- Same computation for all components (drag, reveal, button)
- Computed on-demand when needed (via
S_Hit_Target.hoverColorgetter)
Background Color:
- All components share the same global background from
colors.w_background_colorstore - Reactive - changes propagate to all components automatically
- Used for fill colors and contrast calculations
Inheritance Rules
Thing color flows from Ancestry:
ancestry.thing.color→S_Snapshot.thing_color→Stylescomputation functions- Widgets always use thing color for text/base color
- Dots use thing color for outline color (when not grabbed/editing)
Element color is component-specific:
- Each component sets its own
element_colorviaset_forHovering() - Not inherited from parent/ancestry
- Typically matches
thing.colorbut can differ (e.g., buttons)
- Each component sets its own
Background color is global:
- Single source:
colors.w_background_colorstore - No inheritance - all components read from same store
- Changes affect entire UI simultaneously
- Single source:
Hover color is computed, not inherited:
- Derived from
element_color, not inherited - Computation is consistent:
colors.hover_special_blend(element_color) - Same algorithm for all components
- Derived from
No True Inheritance Hierarchy
Important: Unlike DOM CSS inheritance, colors do NOT cascade from parent to child:
- Child widgets do not inherit colors from parent widgets
- Each component independently computes its colors based on its own state
- Shared colors (like background) come from global stores, not parent components
Color Relationships:
ancestry.thing.color → thing_color → Styles.compute...Colors()
↓
(set via set_forHovering)
↓
element_color → hoverColor (computed) → Styles.compute...Colors()
colors.w_background_color (global store) → Styles.compute...Colors()Current Implementation Status
✅ Implemented correctly:
- Thing color accessed via
S_Snapshot.thing_colorgetter (safe null handling) - Element color set explicitly per component
- Background color from global store
- Hover color computed consistently
No changes needed - Current implementation follows clear patterns without problematic inheritance hierarchies.
Current State Flow
Color states transition based on user interactions and system events. Each state is independent but can combine to produce the final color output.
State Sources:
hover - From
hits.w_s_hoverstore, managed byHitsmanager- Transitions:
none → hover(mouse enters),hover → none(mouse leaves) - Detected via hit testing on mouse move
- Persists while mouse over element
- Transitions:
grabbed - From
ancestry.isGrabbed, managed byx.si_grabs(UX manager)- Transitions:
none → grabbed(item grabbed),grabbed → none(item released) - Set via
ancestry.grab()methods - Persists until explicitly released
- Transitions:
editing - From
ancestry.isEditing, managed bys.w_s_title_edit(Stores)- Transitions:
none → editing(edit starts),editing → none(edit stops) - Set when title editing begins
- Persists until edit completes/cancels
- Transitions:
focus - From
ancestry.isFocus, managed bys.w_ancestry_focus(Stores)- Transitions:
none → focus(focus set),focus → none(focus changes) - Set via
ancestry.becomeFocus() - Single focus at a time (moving focus clears previous)
- Transitions:
selected - From
isSelectedproperty onS_Element- Transitions:
none → selected(selection set),selected → none(deselected) - Used for buttons in selection contexts
- Component-level state (not ancestry-based)
- Transitions:
disabled - From
isDisabledproperty onS_Element- Transitions:
none → disabled(disabled set),disabled → none(enabled) - Component-level state
- Typically static per component instance
- Transitions:
State Transitions:
| State | On | Off |
|---|---|---|
hover | Mouse enters element | Mouse leaves element |
grabbed | Item grabbed (ancestry.grab()) | Item released |
editing | Editing starts | Editing stops |
focus | Focus set (ancestry.becomeFocus()) | Focus changes to another item |
selected | isSelected = true | isSelected = false |
disabled | isDisabled = true | isDisabled = false |
Notes:
- All states can combine simultaneously (e.g.,
hover + grabbed + editing) - States transition independently - no coordination needed between state managers
- Color computation in
Styles.tshandles all state combinations with proper priority
State Collection Pattern:
States are collected via S_Snapshot getters which read from:
hover:S_Hit_Target.isHovering→hits.w_s_hoverstoregrabbed:ancestry.isGrabbed→x.si_grabs(UX manager)editing:ancestry.isEditing→s.w_s_title_edit(Stores)focus:ancestry.isFocus→s.w_ancestry_focus(Stores)selected:this.isSelected(component property)disabled:this.isDisabled(component property)
Current Implementation:
✅ States are collected reactively in getters ✅ S_Snapshot provides unified access to all states ✅ Styles computation functions handle all state combinations ✅ No state transition coordination needed - each state is independent
Migration Strategy
Current State:
- State transitions are handled by respective managers/stores
- No centralized state transition logic
- States transition independently
- Color computation handles all combinations
Potential Improvements:
State Transition Events
- Consider emitting events when states transition
- Could enable analytics/logging
- Could trigger side effects
- Recommendation: Not needed for color management alone
State Validation
- Validate impossible combinations (e.g., disabled + editing)
- Recommendation: Handle in
Stylescomputation if needed (currently not an issue)
State Transition Hooks
- Allow components to react to state transitions
- Recommendation: Use existing reactive patterns (Svelte reactivity already handles this)
State History/Debouncing
- Track state transition history
- Debounce rapid transitions
- Recommendation: Not needed - current reactive system handles rapid changes
Migration Steps (if needed):
If we need to standardize state transitions in the future:
Define State Transition Interface
typescriptinterface StateTransition { from: string; to: string; timestamp: number; source: string; // 'hover', 'grab', 'edit', 'focus', etc. }Add Transition Tracking (Optional)
- Track transitions in respective managers
- Log or emit events
- Use for debugging/analytics
Add Transition Validation (Optional)
- Validate transitions in
Stylescomputation - Handle edge cases (e.g., disabled + editing)
- Validate transitions in
Add Transition Hooks (Optional)
- Allow components to register transition callbacks
- Use for complex side effects
Recommendation:
✅ No changes needed - Current implementation is sufficient:
- States transition independently (correct design)
- Reactive system handles state changes automatically
- Color computation already handles all combinations
- Adding transition tracking would add complexity without clear benefit
Future Considerations:
- If we add state transition analytics → add tracking to managers
- If we need transition validation → add to
Stylescomputation - If we need transition hooks → use Svelte reactive statements (already supported)
Implementation Notes
- ✅ Colors respond to: hover, grab, edit, focus, selected, disabled
- ✅ Inversion logic:
color_isInverted = (isInverted ?? false) !== hover(computed in Styles) - ✅ Background color from
colors.w_background_color - ✅ Thing color from
ancestry.thing?.color(accessed viaS_Snapshot.thing_colorgetter) - ✅ Widget title hover:
S_Snapshot.hoverchecks if widget/title with same ancestry is hovered - ✅ Deprecated code removed:
colorFor_grabbed_andEditing(),isRadial_focus, unusedsvg_outline_colorfallback logic - ✅ Only widgets can have selected, editing or focus state (via ancestry)
Styles manager (Implementation)
Current Pattern: Managers are singleton classes exported from Global_Imports:
Colorsmanager - color scheme management, stores, utility functionsHitsmanager - hit testing, hover detection, click handlingUXmanager (exported asx) - grab/focus/expand state management- Pattern:
export class ManagerName { ... }thenexport const instance = new ManagerName();
Pros of Manager Pattern:
Consistency with existing codebase
- Follows established architectural pattern
- Integrates seamlessly with
Global_Imports - Matches how other computation/state logic is organized
Single instance, shared access
- One instance imported globally (e.g.,
styles.computeWidgetColors()) - No need to pass instances around
- Easy to access from any component
- One instance imported globally (e.g.,
Future extensibility
- Can add state/stores if needed (e.g., caching, computed color themes)
- Can add lifecycle methods (initialization, cleanup)
- Room to grow beyond pure computation
Clear ownership
- Explicitly owns color computation domain
- Clear separation from
Colorsmanager (scheme/stores) vs Styles (computation logic)
Cons of Manager Pattern:
Overhead for pure functions
- Styles is primarily pure computation (functions that take state → return colors)
- Manager pattern adds class instantiation overhead
- State objects (
S_Snapshot) already passed in - no need for manager to hold state
Alternative: Static utility class
- Could be
export class Styles { static computeWidgetColors(...) } - No instance needed for pure functions
- Simpler:
Styles.computeWidgetColors()vsstyles.computeWidgetColors()
- Could be
Separation from Colors manager
Colorsmanager handles color schemes, stores, utility functions- Styles handles computation logic
- Might be confusing having two color-related managers
- Could alternatively live as methods on existing
Colorsmanager
No state management needed
- Unlike
Hits(manages stores, timers) orUX(manages S_Items), Styles has no persistent state - Pure computation doesn't benefit from singleton instance
- All state comes from
S_Snapshotparameter
- Unlike
Implementation:
Used static utility class approach:
export default class Styles { static computeWidgetColors(...), static computeDotColors(...), static computeButtonColors(...) }- Located in
src/lib/ts/utilities/Styles.ts - Pure computation functions - no instance state needed
- Easy to test and reason about
- Clear separation:
Stylescomputes colors,Colorsmanager handles schemes/stores
File Structure:
src/lib/ts/utilities/Styles.ts- Color computation logicsrc/lib/ts/state/S_Snapshot.ts- State object for passing to Stylessrc/lib/ts/state/S_Element.ts- Uses Styles for dot/button colorssrc/lib/ts/state/S_Widget.ts- Uses Styles for widget colors
Design
The t_hover_target property is extracted from the S_Snapshot state object (const t_hover_target = s_go.t_hover_target;) to determine which specific part of a widget is being hovered. This information provides visual treatments that are specific to the hover target type:
| Type | Treatment |
|---|---|
| reveal | widget background -> transparent, border -> transparent |
| title | border -> thing_color directly |
| drag | border -> a special blend of thing_color with faint opacity |
| widget | border -> default background color computation |
This design allows the same widget to display different visual feedback depending on which interactive element within it is being hovered, providing more precise user feedback.
Trade-offs: inject Styles methods into S_Snapshot
Pros of migrating methods into S_Snapshot:
- Closer to the data: Color computation lives next to the state it depends on, which can make the API feel more "object-oriented" (
s_go.widgetColors(...)). - Potentially fewer parameters: Some calls could avoid threading
S_Snapshotas an argument if methods read internal fields directly. - Discoverability from state: Browsing
S_Snapshotshows both the state and the operations available on that state.
Cons of migrating methods into S_Snapshot:
- Mixes concerns:
S_Snapshotstops being a simple state snapshot and becomes a behavior-heavy object, blurring the line between “state carrier” and “color engine”. - Harder to test in isolation: Pure functions in
Stylesare easy to unit-test; methods onS_Snapshotrisk picking up extra dependencies over time. - Tighter coupling: Every change to color logic now touches the state class, increasing churn and making it harder to reuse
S_Snapshotfor non-color purposes later. - Less obvious separation of roles: Right now
S_Snapshot= “state of go” andStyles= “color computation”; merging them weakens the mental model and the ability to swap out or extend the style engine independently.
Analyze set_forHovering
Current Usage Patterns
set_forHovering(element_color: string, hoverCursor: string) is called in multiple places:
| Location | Component Type | Color Source | Cursor | When Called |
|---|---|---|---|---|
Widget_Drag.svelte | Drag dot | thing.color | 'pointer' or 'normal' | In update_colors() (reactive) |
Widget_Reveal.svelte | Reveal dot | thing.color | 'pointer' | In update_colors() (reactive) |
Buttons_Row.svelte | Button | colors.default | 'pointer' | During setup (static) |
D_Actions.svelte | Button | colors.default | 'pointer' | During setup (static) |
D_Traits.svelte | Button | colors.default | 'pointer' | During setup (static) |
D_Selection.svelte | Button | colors.default | 'pointer' | In reactive block (static) |
D_Data.svelte | Button | colors.default or 'black' | 'pointer' | During setup (static) |
Elements.ts | Control | colors.default or 'white' | 'pointer' | In s_control_forType() (static) |
Cluster_Pager.svelte | Pager | color (computed) | 'pointer' | During setup (static) |
Key Observations
Dots (drag/reveal):
- Always use
thing.coloraselement_color - Called reactively in
update_colors()when thing color changes - Cursor depends on state (
'pointer'if interactive,'normal'otherwise)
- Always use
Buttons/Controls:
- Use
colors.default(or occasionally'white'or'black') - Called once during setup (not reactive)
- Always use
'pointer'cursor
- Use
What
set_forHoveringdoes:- Sets
element_color(base stroke/outline color) - Computes
hoverColorviacolors.hover_special_blend(element_color) - Sets
hoverCursor(cursor style)
- Sets
Centralization Opportunities
Option 1: Auto-initialize for dots in S_Element constructor
- For dots: automatically call
set_forHovering(thing_color, 'pointer')in constructor - For buttons/controls: require explicit call (current pattern)
- Pros: Eliminates repetitive calls in Widget_Drag/Widget_Reveal
- Cons: Cursor logic (
'pointer'vs'normal') is state-dependent, can't be determined in constructor
Option 2: Make element_color a getter for dots
- For dots:
get element_color()returnsthing_color(reactive) - For buttons/controls: keep explicit
element_colorproperty - Pros: Fully reactive, no manual updates needed
- Cons: Requires refactoring
S_Hit_Target.element_colorfrom property to getter (breaking change)
Option 3: Centralize in S_Element with setup method
- Add
S_Element.setup_forHovering()that components call - For dots: automatically uses
thing_color - For buttons: accepts explicit color
- Pros: Single place to call, clear intent
- Cons: Still requires calls in each component
Option 4: Reactive element_color for dots only
- Add
element_color_override?: stringproperty - For dots:
get element_color()returnselement_color_override ?? thing_color - For buttons: set
element_color_overrideexplicitly - Pros: Backward compatible, reactive for dots
- Cons: Adds complexity with override pattern
Recommendation
Recommended: Option 2 (Getter for dots) + Helper method
Make
element_colora getter inS_Hit_Target:typescriptprivate _element_color?: string; // Explicit override get element_color(): string { // For dots: use thing_color if no override if (this.isADot && !this._element_color) { return this.ancestry?.thing?.color ?? 'black'; } return this._element_color ?? 'black'; } set element_color(value: string) { this._element_color = value; this.hoverColor = colors.hover_special_blend(value); }Add
set_forHovering()convenience method (keep for backward compatibility):- Sets
element_color(which now auto-computeshoverColor) - Sets
hoverCursor
- Sets
Update components:
- Dots: Remove
set_forHovering()calls fromupdate_colors()- element_color is now reactive - Buttons: Keep explicit
set_forHovering(colors.default, 'pointer')calls - Cursor: For dots, add separate
hoverCursorupdate logic inupdate_colors()if needed
- Dots: Remove
Benefits:
- Eliminates ~10+
set_forHovering()calls in drag/reveal components - Makes dot colors fully reactive to thing color changes
- Maintains explicit control for buttons/controls
- Backward compatible (existing button code unchanged)
Migration Impact:
- Low risk: Dots become more reactive (improvement)
- No breaking changes: Buttons continue to work as-is
- Code reduction: ~20-30 lines eliminated from Widget_Drag/Widget_Reveal
Current Status
Not yet implemented - This is a future optimization opportunity. Current scattered calls work correctly but could be simplified.