Timers
Mouse timing logic centralized in Hits manager. Components declare intent ("I need autorepeat"), manager handles lifecycle. State survives re-renders because it lives on the target, not the component.
Table of Contents
- Overview
- T_Mouse_Detection Enum
- State Management
- Event Flow
- Timing Types
- Component Migration Notes
- Testing
- Risks & Mitigation
- Session Notes
Overview
Before: Distributed Timing
Component → Own Timer → Manual Start/Stop → Component State- Each component manages its own timer lifecycle
- State lives in component (lost on re-render)
- Duplicated logic across components
- Manual hover-leave handling per component
After: Centralized Timing
Component → Declares Intent → Central Manager → Automatic Lifecycle- Single manager owns all timers
- State persists on shared target (survives re-render)
- Logic centralized in one place
- Automatic hover-leave cleanup
Core Principles
- Declaration Over Management — Components set properties (
mouse_detection,*_callback), manager handles timing automatically - State Persistence — Timing state lives on target (
autorepeat_event,autorepeat_isFirstCall,clicks), not component - Enum-Based Configuration — Single enum replaces multiple boolean flags, enforces mutual exclusivity
- Automatic Lifecycle — Mouse down starts timers, mouse up/hover-leave cancels them
- Single Source of Truth — One manager, one timer per timing type
T_Mouse_Detection Enum
The T_Mouse_Detection enum (in Enumerations.ts) uses bit flags:
export enum T_Mouse_Detection {
autorepeat = 4, // Mutually exclusive with others
doubleLong = 3, // double | long (can combine these two)
double = 1,
long = 2,
none = 0,
}S_Hit_Target uses a single mouse_detection property:
// S_Hit_Target.ts
mouse_detection: T_Mouse_Detection = T_Mouse_Detection.none;
longClick_callback?: (s_mouse: S_Mouse) => void;
doubleClick_callback?: (s_mouse: S_Mouse) => void;
clicks: number = 0;
// Getters for Hits logic
get detects_autorepeat(): boolean { return this.mouse_detection === T_Mouse_Detection.autorepeat; }
get detects_longClick(): boolean { return (this.mouse_detection & T_Mouse_Detection.long) !== 0; }
get detects_doubleClick(): boolean { return (this.mouse_detection & T_Mouse_Detection.double) !== 0; }Components pass mouse_detection={T_Mouse_Detection.autorepeat} instead of detect_autorepeat={true}.
State Management
Hits manages centralized timing with:
| Store/Property | Purpose |
|---|---|
w_autorepeat | Target currently autorepeating |
w_longClick | Target waiting for long-click |
autorepeat_timer | Single Mouse_Timer for autorepeat |
click_timer | Single Mouse_Timer for long-click and double-click |
pending_singleClick_target | Target with deferred single-click (double-click detection) |
pending_singleClick_event | MouseEvent for deferred single-click |
longClick_fired | Flag to suppress mouse-up after long-click |
doubleClick_fired | Flag to suppress mouse-up after double-click timer expires |
Event Flow
handle_click_at Flow
On mouse down:
- Increment
target.clicks - If
target.detects_longClick→ start long-click timer, store pending target/event - If
target.detects_doubleClick:- If second click within threshold → fire
doubleClick_callback, cancel timer - If first click → start double-click timer, defer single-click
- If second click within threshold → fire
- If
target.detects_autorepeat→ start autorepeat - If no special detection → fire
handle_s_mouseimmediately
On mouse up:
- Cancel long-click timer
- Stop autorepeat
- Reset
target.clicks - If long-click already fired or double-click timer already fired → suppress regular click
- Otherwise → fire
handle_s_mouse
When long-click timer fires:
- Fire
longClick_callbackwithS_Mouse.long(...) - Set
longClick_firedflag to suppress subsequent mouse-up
When double-click timer expires:
- User didn't click again → fire deferred single-click callback
- Reset
target.clicks - Set
doubleClick_firedflag to suppress subsequent mouse-up click
Cleanup on Hover-Leave
In detect_hovering_at, cancel pending timers if mouse leaves the target:
- If hover leaves the autorepeating target → stop autorepeat
- If hover leaves the long-click target → cancel long-click timer
- If hover leaves the pending double-click target → cancel double-click timer
Timing Types
Autorepeat
Buttons repeatedly fire their action while held down.
Originally per-component:
- Each component got its own
Mouse_Timerviae.mouse_timer_forName(name) - On
s_mouse.isDown, calledmouse_timer.autorepeat_start(id, callback) - On
s_mouse.isUpor hover leave, calledmouse_timer.autorepeat_stop() - Visual feedback via
mouse_timer.isAutorepeating_forID(id)
Now centralized in Hits:
Hitsownsautorepeat_timerplusw_autorepeatstore- Components set properties on
S_Hit_Target:mouse_detection = T_Mouse_Detection.autorepeatautorepeat_callback?: () => voidautorepeat_id?: numberautorepeat_event?: MouseEvent(persists across component recreation)autorepeat_isFirstCall: boolean
- Hits starts/stops autorepeat on mouse down/up
- Visual feedback from
w_autorepeatstore
Components using autorepeat:
| Component | Pattern | Notes |
|---|---|---|
Glow_Button.svelte | mouse_detection={T_Mouse_Detection.autorepeat} | ✅ Migrated |
Button.svelte | mouse_detection prop | ✅ Migrated |
Next_Previous.svelte | Always enabled | ✅ Migrated; each button has its own S_Element |
D_Actions.svelte | mouse_detection={T_Mouse_Detection.autorepeat} | ✅ Migrated |
Steppers.svelte | mouse_detection={T_Mouse_Detection.autorepeat} | ✅ Migrated |
Long-Click
Fires after ~500ms threshold, suppresses subsequent mouse-up click. Centralized in Hits with w_longClick store, using click_timer.
Double-Click
Defers single-click ~200ms, fires double-click on second click within threshold. Centralized in Hits using click_timer.
Click Counting
Moved from S_Mouse.clicks (deprecated) to S_Hit_Target.clicks. Centralized in Hits — increments on down, resets on up or double-click.
Component Migration Notes
Glow_Button.svelte — Low Risk ✅
Migration:
- Removed
Mouse_Timerinstance - Removed autorepeat start/stop calls
- Removed hover leave reactive statement
- Set
s_element.detect_autorepeat,autorepeat_callback,autorepeat_idinonMount - Updated CSS class to use
w_autorepeatstore
Result: ~20 lines of autorepeat logic → 4 lines of property setup
Button.svelte — Medium Risk ✅
Complexity:
- Supports
autorepeat,long,double,doubleLong - Uses
S_Mouse.repeat()vsS_Mouse.down()distinction - Exposes
handle_s_mouseprop, wraps inintercept_handle_s_mouse
Migration:
- Removed autorepeat start/stop calls
- Autorepeat callback uses
autorepeat_isFirstCallflag to distinguish initial vs repeat - Captures mouse event on down for autorepeat callbacks
- LongClick handling remains component-managed
Next_Previous.svelte — Medium-High Risk ✅
Challenge: Multiple buttons (array) previously shared one Mouse_Timer
Migration:
- Each button gets its own
S_Elementhit target s_element.handle_s_mouseset per index- Each button can autorepeat independently
D_Actions.svelte — High Risk ✅
Challenge: Conditional autorepeat — only T_Action.browse and T_Action.move support it
Migration:
- All actions now autorepeat
- Callback logic handles action-specific behavior
Testing
Autorepeat
Basic autorepeat across re-render
- [x] Open actions panel, press and hold a browse action
- [x] Verify: fires immediately, repeats after delay, stops on release
- [x] Re-render test: Selection changes cause UI re-render, autorepeat continues
Hover-leave cancels autorepeat
- [x] Press and hold, drag mouse off button
- [x] Verify: autorepeat stops immediately
Steppers autorepeat
- [x] Click build button, hold stepper triangle
- [x] Verify: value increments/decrements repeatedly
Double-Click
Double-click fires on second click
- [ ] Find button with
T_Mouse_Detection.double - [ ] Click twice quickly (~300ms)
- [ ] Verify: double-click callback fires, single-click does not
- [ ] Find button with
Single-click deferred then fires
- [ ] Click once, wait for threshold
- [ ] Verify: single-click fires after delay
Hover-leave cancels pending double-click
- [ ] Click once, move mouse off button
- [ ] Verify: pending single-click cancelled
Long-Click
Long-click fires after threshold
- [ ] Find button with
T_Mouse_Detection.long - [ ] Press and hold past ~500ms
- [ ] Verify: long-click callback fires
- [ ] Find button with
Long-click suppresses regular click
- [ ] After long-click fires, release mouse
- [ ] Verify: no additional click action
Early release prevents long-click
- [ ] Press, release before threshold
- [ ] Verify: normal click fires, not long-click
Hover-leave cancels long-click timer
- [ ] Press and hold, drag off before threshold
- [ ] Verify: long-click does not fire
Risks & Mitigation
| Risk | Mitigation |
|---|---|
| Long-click cancels single-click | Set longClick_fired flag, check in mouse-up handler |
| Double-click delays single-click | Opt-in per target; document tradeoff |
| Click counting conflicts | Migrate all to S_Hit_Target.clicks; deprecate S_Mouse.clicks |
| Timer conflicts | Each timer tracks its target; cancel on target change |
| Hover-leave edge cases | Cancel pending timers in detect_hovering_at |
| Autorepeat incompatible with long/double | Enforced via T_Mouse_Detection enum |
Session Notes
Historical notes from the timing centralization work.
Button Styling Fixes
Problem: Control buttons had incorrect fill colors (transparent instead of white).
Solution:
- Set
color_backgroundonS_Elementinstances inElements.s_control_forType() - Modified
S_Element.ts:fillgetter usescolor_backgroundas fallback
Files: Elements.ts, S_Element.ts
Close Button Hit Testing (Unresolved)
Problem: Close search button only responded in tiny area at top-left corner.
Attempted: display: block, tick(), bypassing set_html_element(), duplicate entry prevention.
Status: Reverted. Root cause unclear — may be stale RBush entries or timing of rect updates.
Documentation Patterns
Refactoring Guide Pattern
Create notes/design/refactoring-guide.md with principles and process, then add example section.
Geometry Documentation Pattern
Before refactoring, document existing system's responsibilities, invocation patterns, and actors.
Layout Guide Pattern
Create guide with Analysis Questions, Design Questions, Simplification Opportunities, Performance Opportunities, Summary.
Markdown Anchor Pattern
- Promote important bullets to subheaders for stable anchors
- Write
**Problem**/**Goal**bullets inline near context - Create
## Summarywith### Problemsand### Goals - Group by originating section with links back