a reactive (signals based) hypermedia web framework (wip)
stormlightlabs.github.io/volt/
hypermedia
frontend
signals
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.