a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
at main 177 lines 5.0 kB view raw view rendered
1# Pin Scoping 2 3Element references via `data-volt-pin` require a scoping strategy to avoid name collisions and provide predictable access patterns. 4This document explores three approaches that were considered 5 6## Current Implementation: [data-volt] Root Scoping 7 8**How it works:** 9 10- Each `[data-volt]` root has its own pin registry 11- Pins are isolated to their scope 12- `$pins.name` accesses pins within the current root 13 14**Example:** 15 16```html 17<div data-volt> 18 <input data-volt-pin="username" /> 19 <button data-volt-on-click="$pins.username.focus()">Focus</button> 20</div> 21 22<div data-volt> 23 <input data-volt-pin="username" /> <!-- Different registry --> 24 <button data-volt-on-click="$pins.username.focus()">Focus</button> 25</div> 26``` 27 28**Pros:** 29 30- Predictable: Each root is isolated 31- No name collision risk across roots 32- Aligns with current scope model (one scope per root) 33- Simple to implement and reason about 34 35**Cons:** 36 37- Can't share pins across roots (must use global state instead) 38- No sub-scoping within a root for component-like patterns 39 40**Use Cases:** 41 42- Simple applications with clear root boundaries 43- When each `[data-volt]` represents a distinct feature/component 44 45--- 46 47## Alternative 1: Explicit Scope Boundaries (data-volt-scope) 48 49**How it works:** 50 51- Introduce `data-volt-scope` attribute to create nested scopes 52- Pins registered within a scope are isolated to that scope and its descendants 53- `$pins` searches up the scope chain 54 55**Example:** 56 57```html 58<div data-volt> 59 <div data-volt-scope="form1"> 60 <input data-volt-pin="username" /> 61 <button data-volt-on-click="$pins.username.focus()">Focus</button> 62 </div> 63 64 <div data-volt-scope="form2"> 65 <input data-volt-pin="username" /> <!-- Different scope --> 66 <button data-volt-on-click="$pins.username.focus()">Focus</button> 67 </div> 68</div> 69``` 70 71**Pros:** 72 73- Fine-grained control over scope boundaries 74- Supports nested component patterns 75- Can isolate widgets within a larger root 76 77**Cons:** 78 79- More complex: requires scope hierarchy tracking 80- Additional attribute to learn 81- Lookup complexity (walking scope chain) 82- Breaks current 1:1 scope-to-root model 83 84**Use Cases:** 85 86- Large applications with reusable sub-components 87- When you need multiple isolated widgets within one root 88- Form libraries with nested fieldsets 89 90**Implementation Complexity:** 91 92- Requires scope hierarchy (parent references) 93- WeakMap must track scope chains 94- Pin lookup becomes recursive 95 96## Alternative 2: Global Document-Wide Registry 97 98**How it works:** 99 100- Single global pin registry for entire document 101- All pins accessible from any scope 102- Names must be unique across the entire page 103 104**Example:** 105 106```html 107<div data-volt> 108 <input data-volt-pin="username" /> 109</div> 110 111<div data-volt> 112 <!-- Can access pins from other roots --> 113 <button data-volt-on-click="$pins.username.focus()">Focus</button> 114</div> 115``` 116 117**Pros:** 118 119- Simplest to understand: flat namespace 120- Easy to share element references across roots 121- No scoping complexity 122 123**Cons:** 124 125- High risk of name collisions 126- No isolation between roots (breaks encapsulation) 127- Debugging becomes harder (where is this pin defined?) 128- Not composable (can't have two instances of same component) 129 130**Use Cases:** 131 132- Prototypes and simple pages 133- Single-page applications with unique IDs everywhere 134- When cross-root communication is primary goal 135 136**Implementation Complexity:** 137 138- Simple: single Map instead of per-scope maps 139- No WeakMap needed 140 141## Comparison Table 142 143| Aspect | Root Scoping (Current) | Explicit Scopes | Global | 144|--------|------------------------|-----------------|--------| 145| Isolation | Per root | Per scope boundary | None | 146| Name Collisions | Safe within root | Safe within scope | High risk | 147| Complexity | Low | Medium | Very Low | 148| Cross-root Access | Not supported | Not supported | Supported | 149| Nested Components | Not supported | Supported | Not needed | 150| Implementation | Simple | Complex | Trivial | 151| Composability | Good | Excellent | Poor | 152 153## Decision Rationale 154 155**Current implementation uses Root Scoping** for the following reasons: 156 1571. **Aligns with existing architecture**: VoltX already uses one scope per `[data-volt]` root 1582. **Simplicity**: No additional concepts or attributes to learn 1593. **Good enough**: Most use cases don't require nested scopes 1604. **Future extensibility**: Can add `data-volt-scope` later if needed (additive change) 161 162**When to reconsider:** 163 164- If users frequently request nested component isolation 165- If framework adds first-class component system 166- If cross-root pin access becomes a common need (could add `data-volt-pin-global`) 167 168## Migration Path 169 170If we later adopt Alternative 1 (Explicit Scopes): 171 1721. Keep current behavior as default 1732. Add `data-volt-scope` for opt-in nested scopes 1743. Update metadata to track parent scopes 1754. Modify `getPin()` to walk scope chain 176 177This would be backward compatible since existing code without `data-volt-scope` would continue to work.