vectorvesper 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hooks.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
3
|
"engineVersion": "0.3.0",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-27T08:10:50.379Z",
|
|
5
5
|
"contractLevel": "lean",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"tagline": "Device capability fused with live frame health — the complete quality signal.",
|
|
16
16
|
"problem": "Measured frame health is the truth but is blind for the first seconds; device signals guess instantly but never learn. Using either alone gets one of those wrong.",
|
|
17
17
|
"summary": "Probes the device once (WebGL2, GPU renderer string, memory, cores, reduced-motion) and fuses that with the live AnimationBudget as `tier = max(deviceTier, budgetTier)`. The device tier is a FLOOR, and that is the whole point: without it, weak devices oscillate forever — degrade, frames recover because you degraded, upgrade, jank, degrade. A software renderer stays a software renderer. `reasons` explains the device verdict.",
|
|
18
|
+
"mechanism": "Another thin subscription; the substance is the AdaptiveQuality singleton, which fuses two signals that are each useless alone. The live budget measures truth, but only once frames have accumulated, so the opening seconds of a page are blind. Device heuristics answer instantly but never learn.\n\nThe fusion rule is a maximum, not an average: the device tier is a floor that the live budget can drop quality below but never lift quality above. Without that floor, weak devices oscillate — degrade, frames recover precisely because you degraded, upgrade, jank, degrade again.\n\nDevice tier comes from a single WebGL2 probe taken once and cached. No WebGL2 at all is tier 2 immediately. A software renderer (SwiftShader, llvmpipe) is tier 2. A mobile-class GPU is tier 1 — those run GL perfectly well, but inside a thermal envelope that a sustained effect will find. Four or fewer cores, or four or fewer gigabytes of reported memory, adds one tier on top. The probe releases its GL context immediately rather than holding one open for the page's lifetime, and every branch appends a plain-English string to reasons, so the verdict is explainable instead of a magic number.\n\nThere is no GPU benchmark database anywhere in this, deliberately. The heuristic is conservative and then corrected by real measurement, which ages better than a lookup table of hardware that did not exist when it was written.",
|
|
18
19
|
"signature": "useAdaptiveQuality(): AdaptiveState",
|
|
19
20
|
"options": [],
|
|
20
21
|
"returns": [
|
|
@@ -69,7 +70,8 @@
|
|
|
69
70
|
"disclosure": {
|
|
70
71
|
"exposeImplementation": false,
|
|
71
72
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
72
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
73
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
74
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
73
75
|
},
|
|
74
76
|
"runtime": {
|
|
75
77
|
"requiresClient": true,
|
|
@@ -79,9 +81,17 @@
|
|
|
79
81
|
"conflictsWith": [],
|
|
80
82
|
"usesPointer": false,
|
|
81
83
|
"usesScroll": false,
|
|
82
|
-
"usesWebGL": true
|
|
84
|
+
"usesWebGL": true,
|
|
85
|
+
"lane": "input",
|
|
86
|
+
"priority": "essential",
|
|
87
|
+
"reRendersPerFrame": 0,
|
|
88
|
+
"sharedSingletons": [
|
|
89
|
+
"FrameConductor",
|
|
90
|
+
"AnimationBudget",
|
|
91
|
+
"AdaptiveQuality"
|
|
92
|
+
]
|
|
83
93
|
},
|
|
84
|
-
"upgrade": "This is the lean contract: enough to
|
|
94
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
85
95
|
},
|
|
86
96
|
{
|
|
87
97
|
"contractLevel": "lean",
|
|
@@ -94,6 +104,7 @@
|
|
|
94
104
|
"tagline": "A live quality tier from measured frame health, so effects can shed themselves.",
|
|
95
105
|
"problem": "A page looks fine on the machine it was built on and janks on the device it ships to, and nothing in the code knows the difference.",
|
|
96
106
|
"summary": "Measures real frame intervals on the shared conductor and collapses them into a coarse tier every effect can consume. Hysteresis is asymmetric on purpose: degrading is fast (users feel jank within a second), recovering takes 8 seconds of clean frames, so quality never flaps. Since v0.3 the thresholds are relative to the measured display refresh rate, so 70fps on a 120Hz panel correctly reads as degraded.",
|
|
107
|
+
"mechanism": "The hook is a three-line subscription — a snapshot in React state, updated by the AnimationBudget singleton for the life of the component. Everything interesting is in the governor.\n\nIt measures from the conductor's input lane at essential priority, keeping a rolling window of 90 frames and refusing to return any verdict before 40 samples have accumulated. A page is always janky during hydration, and a governor that judged it there would degrade every site on load. A frame counts as slow past 1.11x the frame budget and very slow past 2.04x, measured against the detected display rate rather than an assumed 60Hz.\n\nTier changes are hysteresis-gated and deliberately asymmetric. Degradation triggers when 30 percent of the window is slow, or 15 percent is very slow, and jumps straight to tier 2 when 30 percent is very slow — with a 1.5 second cooldown between steps. Recovery is much harder to earn: under 6 percent slow, sustained for a full 8 seconds. That asymmetry is the whole point. Symmetric thresholds oscillate, because the page degrades, frames recover precisely because it degraded, it upgrades, it janks again, forever.\n\nHeadroom is a separate exponentially-smoothed signal (alpha 0.5) describing how much of each frame is still unspoken for, and a spike guard keeps one 150ms stall from poisoning the average. Emissions happen on tier change and on a 0.5 second heartbeat for telemetry displays — which is exactly why React state is the right shape here, and why reading this inside a frame callback is the wrong one.",
|
|
97
108
|
"signature": "useAnimationBudget(): BudgetState",
|
|
98
109
|
"options": [],
|
|
99
110
|
"returns": [
|
|
@@ -153,7 +164,8 @@
|
|
|
153
164
|
"disclosure": {
|
|
154
165
|
"exposeImplementation": false,
|
|
155
166
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
156
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
167
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
168
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
157
169
|
},
|
|
158
170
|
"runtime": {
|
|
159
171
|
"requiresClient": true,
|
|
@@ -163,9 +175,16 @@
|
|
|
163
175
|
"conflictsWith": [],
|
|
164
176
|
"usesPointer": false,
|
|
165
177
|
"usesScroll": false,
|
|
166
|
-
"usesWebGL": false
|
|
178
|
+
"usesWebGL": false,
|
|
179
|
+
"lane": "input",
|
|
180
|
+
"priority": "essential",
|
|
181
|
+
"reRendersPerFrame": 0,
|
|
182
|
+
"sharedSingletons": [
|
|
183
|
+
"FrameConductor",
|
|
184
|
+
"AnimationBudget"
|
|
185
|
+
]
|
|
167
186
|
},
|
|
168
|
-
"upgrade": "This is the lean contract: enough to
|
|
187
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
169
188
|
},
|
|
170
189
|
{
|
|
171
190
|
"contractLevel": "lean",
|
|
@@ -178,6 +197,7 @@
|
|
|
178
197
|
"tagline": "Images spawn along the pointer's path and fade — the award-site gallery flourish.",
|
|
179
198
|
"problem": "The tutorial version creates a DOM node per pointer move and lets the garbage collector deal with it, which is exactly the allocation pattern that produces stutter.",
|
|
180
199
|
"summary": "A fixed pool of `<img>` nodes is created once and recycled. Each flight is animated with the Web Animations API, so there is no rAF loop and nothing is allocated per move — the oldest flight is simply cancelled and reused. Spawns are distance-gated, not event-gated, so the spacing stays even at any pointer speed. Fails open (does nothing) on touch devices and under reduced motion.",
|
|
200
|
+
"mechanism": "Object pooling plus the Web Animations API, and deliberately no frame loop at all. A fixed set of img elements — 10 by default — is created once on mount, positioned absolutely inside the container, and recycled round-robin. Nothing is allocated per pointer move, which is what separates this from the tutorial version that creates and destroys a node on every spawn and hands the garbage collector a steady stream of work during the exact interaction that needs to stay smooth.\n\nSpawning is distance-gated rather than time-gated: a new image appears only once the pointer has travelled the spacing threshold since the last one, compared as squared distance so the square root is never taken. Time-gating would clump images when the cursor moves slowly and stretch them apart when it moves fast; distance-gating makes the trail read identically at any speed.\n\nEach flight is a single three-keyframe Web Animations call — fade in while rising and scaling up, then fade out while drifting down — with a small randomised drift and tilt so repeated spawns do not look stamped from a template. Handing the animation to the browser's compositor means there is no per-frame JavaScript for the images whatsoever. When the pool wraps around, any animation still running on the reused node is cancelled first, so the oldest flight is stolen rather than layered.\n\nThis hook is intentionally self-contained: a local pointermove listener on the container, no SensorBus, no conductor subscription. It fails open on touch devices and under reduced motion, and it restores the container's original position and overflow on unmount.",
|
|
181
201
|
"signature": "useImageTrail<T>(options: UseImageTrailOptions): { ref }",
|
|
182
202
|
"options": [
|
|
183
203
|
{
|
|
@@ -243,7 +263,8 @@
|
|
|
243
263
|
"disclosure": {
|
|
244
264
|
"exposeImplementation": false,
|
|
245
265
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
246
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
266
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
267
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
247
268
|
},
|
|
248
269
|
"runtime": {
|
|
249
270
|
"requiresClient": true,
|
|
@@ -255,9 +276,13 @@
|
|
|
255
276
|
],
|
|
256
277
|
"usesPointer": true,
|
|
257
278
|
"usesScroll": false,
|
|
258
|
-
"usesWebGL": false
|
|
279
|
+
"usesWebGL": false,
|
|
280
|
+
"lane": null,
|
|
281
|
+
"priority": null,
|
|
282
|
+
"reRendersPerFrame": 0,
|
|
283
|
+
"sharedSingletons": []
|
|
259
284
|
},
|
|
260
|
-
"upgrade": "This is the lean contract: enough to
|
|
285
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
261
286
|
},
|
|
262
287
|
{
|
|
263
288
|
"contractLevel": "lean",
|
|
@@ -270,6 +295,7 @@
|
|
|
270
295
|
"tagline": "Mount a heavy scene when it's near the viewport, the thread is idle, and frames are healthy.",
|
|
271
296
|
"problem": "A WebGL canvas three screens down initialises during page load, competing with the content the user is actually looking at.",
|
|
272
297
|
"summary": "Three gates in sequence: an IntersectionObserver with a generous rootMargin so the scene is ready just before it is seen; an idle-callback so it doesn't land mid-scroll; and a scroll-velocity check that defers while the user is actively moving. Optionally waits for the frame budget to recover, with a 3-second fail-open so a permanently busy page still eventually renders. Once mounted it stays mounted.",
|
|
298
|
+
"mechanism": "Four gates in series, then a latch. An IntersectionObserver with a rootMargin — 200px by default — starts the sequence before the element is actually visible, so the work overlaps the remaining scroll distance instead of beginning when the user has already arrived. Leaving the margin cancels everything cleanly and re-arms.\n\nNext it waits for requestIdleCallback with a 1500ms timeout, so the mount lands in a slot the browser has already declared spare rather than competing with whatever else is running.\n\nThen it reads scroll velocity from the SensorBus, which it retains for exactly this purpose. Above roughly 40 pixels per second the mount is deferred and retried from the update lane every frame until scrolling genuinely settles — mounting a WebGL canvas mid-flick is how a smooth scroll becomes a stutter.\n\nThe optional budget gate then holds off while the animation budget sits at tier 2 and waits for recovery, but with a three-second fail-open timeout, because a permanently slow device would otherwise never see the content at all. Blank is worse than degraded.\n\nOnce it fires, the observer disconnects and ready stays true permanently. Nothing unmounts on scroll-away: the remount cost would exceed whatever the unmount saved, and the flicker would be visible.",
|
|
273
299
|
"signature": "useLazyScene<T>(options?: UseLazySceneOptions): { ref, ready }",
|
|
274
300
|
"options": [
|
|
275
301
|
{
|
|
@@ -326,7 +352,8 @@
|
|
|
326
352
|
"disclosure": {
|
|
327
353
|
"exposeImplementation": false,
|
|
328
354
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
329
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
355
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
356
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
330
357
|
},
|
|
331
358
|
"runtime": {
|
|
332
359
|
"requiresClient": true,
|
|
@@ -336,9 +363,17 @@
|
|
|
336
363
|
"conflictsWith": [],
|
|
337
364
|
"usesPointer": false,
|
|
338
365
|
"usesScroll": true,
|
|
339
|
-
"usesWebGL": false
|
|
366
|
+
"usesWebGL": false,
|
|
367
|
+
"lane": "update",
|
|
368
|
+
"priority": "essential",
|
|
369
|
+
"reRendersPerFrame": 0,
|
|
370
|
+
"sharedSingletons": [
|
|
371
|
+
"FrameConductor",
|
|
372
|
+
"SensorBus",
|
|
373
|
+
"AnimationBudget"
|
|
374
|
+
]
|
|
340
375
|
},
|
|
341
|
-
"upgrade": "This is the lean contract: enough to
|
|
376
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
342
377
|
},
|
|
343
378
|
{
|
|
344
379
|
"contractLevel": "lean",
|
|
@@ -351,6 +386,7 @@
|
|
|
351
386
|
"tagline": "A magnetic button that starts reaching before the cursor gets there.",
|
|
352
387
|
"problem": "Every magnetic-button tutorial reacts to hover, so the element only starts moving once the cursor has already arrived — which is exactly too late to feel alive.",
|
|
353
388
|
"summary": "Engagement is max(PointerIntent confidence, proximity falloff), so the element begins reaching while the cursor is still on its way — the pre-touch that makes award-site buttons feel intentional. Writes only `transform`, and restores the previous inline transform exactly on unmount. Fails open to a perfectly normal element on touch-only devices and under reduced motion.",
|
|
389
|
+
"mechanism": "Two subscriptions in two different lanes, and that split is the entire design. Measurement runs in the input lane at essential priority; the transform write runs in the render lane at enhanced. An earlier version read the bounding rect immediately before writing transform in one place, and with two magnetic elements on a page that becomes read, write, read — where the second read is a forced synchronous layout, because the first write dirtied the tree. That is layout thrash scaling with element count, inside the engine whose whole premise is reads before writes. Splitting the lanes means every magnetic element on the page has finished reading before any of them writes.\n\nThe anchor is cached and re-measured only on scroll, resize, or a one-second heartbeat, so the steady state costs no layout reads at all. Measurement subtracts the element's own current offset, otherwise the target chases itself away from the cursor.\n\nEngagement is the maximum of two independent signals: proximity, which is distance from the anchor normalised against reach, and PointerIntent confidence, which is what lets the element begin reaching while the cursor is still approaching rather than only reacting once it arrives. Translation is the vector toward the pointer scaled by engagement, clamped to the strength ceiling, then damped at k = 12. Scale is centre-origin, so it never shifts the anchor it was measured against.\n\nIt declines to run when there is no fine pointer at all — tested with any-pointer rather than coarse pointer, so a touchscreen laptop with a trackpad still gets the effect — and bails entirely under reduced motion, since this hook is itself the motion. The element's previous inline transform is captured on mount and restored exactly on unmount: it owns that property while alive, and gives it back.",
|
|
354
390
|
"signature": "useMagneticIntent<T>(options?: MagneticOptions): { ref, active }",
|
|
355
391
|
"options": [
|
|
356
392
|
{
|
|
@@ -421,7 +457,8 @@
|
|
|
421
457
|
"disclosure": {
|
|
422
458
|
"exposeImplementation": false,
|
|
423
459
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
424
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
460
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
461
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
425
462
|
},
|
|
426
463
|
"runtime": {
|
|
427
464
|
"requiresClient": true,
|
|
@@ -434,9 +471,17 @@
|
|
|
434
471
|
],
|
|
435
472
|
"usesPointer": true,
|
|
436
473
|
"usesScroll": false,
|
|
437
|
-
"usesWebGL": false
|
|
474
|
+
"usesWebGL": false,
|
|
475
|
+
"lane": "render",
|
|
476
|
+
"priority": "enhanced",
|
|
477
|
+
"reRendersPerFrame": 0,
|
|
478
|
+
"sharedSingletons": [
|
|
479
|
+
"FrameConductor",
|
|
480
|
+
"SensorBus",
|
|
481
|
+
"PointerIntent"
|
|
482
|
+
]
|
|
438
483
|
},
|
|
439
|
-
"upgrade": "This is the lean contract: enough to
|
|
484
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
440
485
|
},
|
|
441
486
|
{
|
|
442
487
|
"contractLevel": "lean",
|
|
@@ -449,6 +494,7 @@
|
|
|
449
494
|
"tagline": "Animate a number to its target without re-rendering anything.",
|
|
450
495
|
"problem": "The obvious implementation re-renders the component sixty times a second to change one string, dragging every sibling through reconciliation for a counter.",
|
|
451
496
|
"summary": "Writes `textContent` directly on the target node from the shared frame loop, so React renders exactly once regardless of how far the number travels. Damping is frame-rate-independent, so it feels identical at 30 and 120fps, and it snaps and stops when close enough rather than easing forever. Enforces tabular figures so digits don't wobble the layout, and jumps straight to the target under reduced motion.",
|
|
497
|
+
"mechanism": "The animating value never enters React state. One ref holds the currently displayed number, another holds the target, and a subscription on the update lane at enhanced priority damps the first toward the second and writes the result straight to textContent. A counter animating at 120Hz through state would re-render its subtree 120 times a second to change a string.\n\nDamping is the same frame-rate-independent exponential curve used throughout the runtime, at k = 6 by default, so the deceleration feels identical regardless of display rate. Once the remaining distance falls below 0.01 it snaps to the target exactly — otherwise the asymptote leaves the value permanently a fraction short and the loop never stops having work to do.\n\nThe Intl.NumberFormat instance is constructed once in an effect and cached in a ref rather than built inside the tick, because formatter construction is expensive enough to show up in a per-frame path. Values are rounded to two decimals before formatting so fractional counts stay readable.\n\nIt sets fontVariantNumeric to tabular-nums on the element itself. Proportional digits have different widths, so a number running through them visibly shudders as it counts, and the fix belongs with the behaviour rather than in the consumer's stylesheet.\n\nUnder reduced motion it writes the target once and never subscribes to the loop at all.",
|
|
452
498
|
"signature": "useNumberTicker<T>(value: number, options?: UseNumberTickerOptions): { ref }",
|
|
453
499
|
"options": [
|
|
454
500
|
{
|
|
@@ -514,7 +560,8 @@
|
|
|
514
560
|
"disclosure": {
|
|
515
561
|
"exposeImplementation": false,
|
|
516
562
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
517
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
563
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
564
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
518
565
|
},
|
|
519
566
|
"runtime": {
|
|
520
567
|
"requiresClient": true,
|
|
@@ -526,9 +573,15 @@
|
|
|
526
573
|
],
|
|
527
574
|
"usesPointer": false,
|
|
528
575
|
"usesScroll": false,
|
|
529
|
-
"usesWebGL": false
|
|
576
|
+
"usesWebGL": false,
|
|
577
|
+
"lane": "update",
|
|
578
|
+
"priority": "enhanced",
|
|
579
|
+
"reRendersPerFrame": 0,
|
|
580
|
+
"sharedSingletons": [
|
|
581
|
+
"FrameConductor"
|
|
582
|
+
]
|
|
530
583
|
},
|
|
531
|
-
"upgrade": "This is the lean contract: enough to
|
|
584
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
532
585
|
},
|
|
533
586
|
{
|
|
534
587
|
"contractLevel": "lean",
|
|
@@ -541,6 +594,7 @@
|
|
|
541
594
|
"tagline": "Know the pointer is coming 100–300ms before it arrives, and pre-warm.",
|
|
542
595
|
"problem": "Expensive hover states start their work at the moment of hover, so the first 200ms of every interaction is the user watching something load.",
|
|
543
596
|
"summary": "Casts the pointer's smoothed trajectory forward from the SensorBus and measures time-to-impact against the element's inflated rect. Confidence rises as impact nears; being inside is confidence 1. Enter/exit hysteresis keeps the boolean calm. Use it to start the video, compile the shader or fetch the preview before the cursor lands.",
|
|
597
|
+
"mechanism": "Prediction by ray casting, not by hover. Each frame in the update lane at enhanced priority — after the SensorBus has produced this frame's smoothed pointer velocity — it casts the velocity vector forward and solves for the first moment that ray enters the element's rect, using a slab intersection. That time-to-impact, measured against the horizon (0.5 seconds by default), becomes a raw 0-to-1 target; a pointer already inside returns a time of zero and therefore full confidence.\n\nThe raw target is smoothed with exponential damping at k = 10, and the boolean is hysteresis-gated with separate enter and exit thresholds (0.35 and 0.18) so a cursor lingering near the boundary cannot chatter the state on and off. A minimum speed floor suppresses predictions from a drifting or resting pointer, whose velocity vector carries no real intent.\n\nThe rect is cached. The layout read repeats only when something plausibly moved the element — page scroll, viewport resize — or when a one-second heartbeat elapses, which bounds how stale the cache can get from sticky headers, carousels and async layout shifts it cannot infer on its own. Dynamic mode re-measures every frame once approach begins, for targets that are themselves moving. The steady state performs no layout reads and allocates nothing.\n\nThe two outputs are deliberately different shapes. The boolean goes through React state because it changes rarely and its job is to gate conditional rendering. Confidence is mirrored into a ref on the render lane instead, because a value that changes every single frame must never become a re-render.\n\nThe returned ref is a hybrid — a callback ref that also exposes a current property — so the mount effect re-arms for conditionally rendered elements instead of silently never attaching.",
|
|
544
598
|
"signature": "usePointerIntent<T>(options?: UsePointerIntentOptions): UsePointerIntentReturn<T>",
|
|
545
599
|
"options": [
|
|
546
600
|
{
|
|
@@ -630,7 +684,8 @@
|
|
|
630
684
|
"disclosure": {
|
|
631
685
|
"exposeImplementation": false,
|
|
632
686
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
633
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
687
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
688
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
634
689
|
},
|
|
635
690
|
"runtime": {
|
|
636
691
|
"requiresClient": true,
|
|
@@ -640,9 +695,16 @@
|
|
|
640
695
|
"conflictsWith": [],
|
|
641
696
|
"usesPointer": true,
|
|
642
697
|
"usesScroll": false,
|
|
643
|
-
"usesWebGL": false
|
|
698
|
+
"usesWebGL": false,
|
|
699
|
+
"lane": "update",
|
|
700
|
+
"priority": "enhanced",
|
|
701
|
+
"reRendersPerFrame": 0,
|
|
702
|
+
"sharedSingletons": [
|
|
703
|
+
"FrameConductor",
|
|
704
|
+
"SensorBus"
|
|
705
|
+
]
|
|
644
706
|
},
|
|
645
|
-
"upgrade": "This is the lean contract: enough to
|
|
707
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
646
708
|
},
|
|
647
709
|
{
|
|
648
710
|
"contractLevel": "lean",
|
|
@@ -655,6 +717,7 @@
|
|
|
655
717
|
"tagline": "Hold an expensive mount until the main thread can actually absorb it.",
|
|
656
718
|
"problem": "An expensive component mounts during hydration, when the thread is already saturated, and its cost lands on top of the worst moment of the page's life.",
|
|
657
719
|
"summary": "Watches the frame loop and returns `true` only after a run of frames with real headroom to spare. One-way: once true it never goes back to false, because a component that unmounted itself the moment it made the page slow would oscillate forever. A hardware core-count floor short-circuits the whole check on very weak devices.",
|
|
720
|
+
"mechanism": "A gate that opens once and never closes. It returns false, then true, and stays true for the rest of the mount — an expensive component that unmounted itself the moment it made the page slow would oscillate forever, so the one-way behaviour is deliberate.\n\nThree conditions, in order. A static hardware floor reads logical cores and gives up permanently below the threshold, because core count is the one signal that cannot improve while the page is open. It then holds the AnimationBudget open with an empty subscription: the governor only measures while something is subscribed, so polling an unsubscribed one would read a frozen snapshot forever. Finally it counts consecutive clean frames from the input lane at essential priority, subscribed after the governor's own measure pass so it reads state produced by this very frame. Any frame below the headroom threshold resets the count to zero.\n\nIt polls per frame rather than per budget emission on purpose. The budget only emits on tier changes and a 2Hz heartbeat, which would quietly turn a three-clean-frame requirement into a second and a half of waiting.\n\nThere is deliberately no shortcut for an already-healthy page. Opening immediately would require a synchronous state update inside the effect and a cascading render, and it cannot move into render without breaking hydration — the server has no frame timings, so the first client render must agree with it and start false. Waiting the same three frames everywhere costs roughly 50ms before an expensive mount, which buys one code path instead of two.",
|
|
658
721
|
"signature": "useSafeToMount(options?: UseSafeToMountOptions): boolean",
|
|
659
722
|
"options": [
|
|
660
723
|
{
|
|
@@ -706,7 +769,8 @@
|
|
|
706
769
|
"disclosure": {
|
|
707
770
|
"exposeImplementation": false,
|
|
708
771
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
709
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
772
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
773
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
710
774
|
},
|
|
711
775
|
"runtime": {
|
|
712
776
|
"requiresClient": true,
|
|
@@ -716,9 +780,16 @@
|
|
|
716
780
|
"conflictsWith": [],
|
|
717
781
|
"usesPointer": false,
|
|
718
782
|
"usesScroll": false,
|
|
719
|
-
"usesWebGL": false
|
|
783
|
+
"usesWebGL": false,
|
|
784
|
+
"lane": "input",
|
|
785
|
+
"priority": "essential",
|
|
786
|
+
"reRendersPerFrame": 0,
|
|
787
|
+
"sharedSingletons": [
|
|
788
|
+
"FrameConductor",
|
|
789
|
+
"AnimationBudget"
|
|
790
|
+
]
|
|
720
791
|
},
|
|
721
|
-
"upgrade": "This is the lean contract: enough to
|
|
792
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
722
793
|
},
|
|
723
794
|
{
|
|
724
795
|
"contractLevel": "lean",
|
|
@@ -731,6 +802,7 @@
|
|
|
731
802
|
"tagline": "One set of pointer, scroll and viewport listeners for the whole page.",
|
|
732
803
|
"problem": "Ten effects that each attach their own pointermove and scroll listeners, and each compute their own velocity, do ten times the work to answer one question.",
|
|
733
804
|
"summary": "Retains a page-wide singleton that attaches passive listeners once and computes smoothed derivatives once per frame, in the conductor's input lane — before any consumer runs. Consumers READ a live snapshot during their own frame work. There are no per-event callbacks; the frame loop is the delivery mechanism. Lifecycle is ref-counted, so listeners detach when the last consumer unmounts.",
|
|
805
|
+
"mechanism": "A single module-level SensorBus instance is created lazily on first use and shared by the whole page; the hook itself is only a lifecycle wrapper that grabs that instance and retains it. Lifecycle is ref-counted — the first retain attaches the listeners, and the release closure handed to each consumer detaches them when the last one unmounts, so fifty components cost exactly one set of listeners.\n\nThe listeners do almost nothing. Passive pointermove, pointerdown, pointerup and pointercancel handlers on window write coordinates and a down flag onto plain object fields — no computation, no state updates. Scroll is not listened to at all: scrollX and scrollY are read directly inside the frame tick, which sidesteps scroll-event throttling differences between browsers and costs nothing extra, since the loop is already running. Viewport width, height and devicePixelRatio are resynced the same way, so there is no resize listener either.\n\nAll the math happens once per frame in the conductor's input lane at essential priority, which is what guarantees consumers read values computed for this frame rather than the last one. Velocity for both pointer and scroll is an instantaneous delta over dt, then smoothed with frame-rate-independent exponential damping at k = 14 — not a rolling average, so the feel is identical at 30, 60 and 120Hz. Pointer speed is the hypotenuse of the two smoothed components. Stopping zeroes the velocities, so a later remount does not inherit stale motion.\n\nConsumers never receive a callback. They read a snapshot whose objects are live and mutated in place, which is why the reference is stable across renders and why nothing here ever triggers a React update — the frame loop is the delivery mechanism, not an event emitter.",
|
|
734
806
|
"signature": "useSensorBus(): SensorBus",
|
|
735
807
|
"options": [],
|
|
736
808
|
"returns": [
|
|
@@ -760,7 +832,8 @@
|
|
|
760
832
|
"disclosure": {
|
|
761
833
|
"exposeImplementation": false,
|
|
762
834
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
763
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
835
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
836
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
764
837
|
},
|
|
765
838
|
"runtime": {
|
|
766
839
|
"requiresClient": true,
|
|
@@ -770,9 +843,16 @@
|
|
|
770
843
|
"conflictsWith": [],
|
|
771
844
|
"usesPointer": true,
|
|
772
845
|
"usesScroll": true,
|
|
773
|
-
"usesWebGL": false
|
|
846
|
+
"usesWebGL": false,
|
|
847
|
+
"lane": "input",
|
|
848
|
+
"priority": "essential",
|
|
849
|
+
"reRendersPerFrame": 0,
|
|
850
|
+
"sharedSingletons": [
|
|
851
|
+
"FrameConductor",
|
|
852
|
+
"SensorBus"
|
|
853
|
+
]
|
|
774
854
|
},
|
|
775
|
-
"upgrade": "This is the lean contract: enough to
|
|
855
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
776
856
|
},
|
|
777
857
|
{
|
|
778
858
|
"contractLevel": "lean",
|
|
@@ -785,6 +865,7 @@
|
|
|
785
865
|
"tagline": "Drive a video timeline from scroll or pointer, without the seek stutter.",
|
|
786
866
|
"problem": "Setting `video.currentTime` on every scroll event queues seeks faster than the decoder can serve them, and the result stutters — worst on Safari, worst on the demo you filmed.",
|
|
787
867
|
"summary": "Seek discipline is the whole product: never issue a seek while one is in flight, skip sub-frame deltas, use `fastSeek` for large jumps, and throttle adaptively when the decoder is measurably struggling. Also handles iOS priming (a muted play/pause round trip so Safari actually buffers), records and restores the video's attributes exactly, and stops seeking entirely when the track is off screen.",
|
|
868
|
+
"mechanism": "Progress is a 0-to-1 number recomputed each frame in the update lane at essential priority. Essential, because this is direct manipulation — a scrub that stutters under load reads as broken rather than as tastefully degraded.\n\nUnder the scroll driver, position comes from the SensorBus rather than a scroll listener, and track geometry is measured by walking offsetParent once, then re-measured only when the viewport actually changes size. Two mappings exist: pin, where progress runs from the track's top docking at the viewport top to its bottom reaching the viewport bottom (the sticky scrollytelling pattern), and cross, which spans a full viewport traversal. Auto chooses pin when the track is taller than about 1.2 viewports. The target is then damped with the usual exponential curve at k = 8 by default, or tracked instantly when smoothing is set to zero.\n\nThe seeking logic is where most of the care sits. Deltas below one-sixtieth of a second are discarded as sub-frame noise instead of being issued as seeks. Past a 0.35 second jump it switches to fast-seek, accepting keyframe-accurate positioning in exchange for not decoding every intervening frame. It listens to the element's own seeking and seeked events so it never stacks a new seek onto an incomplete one, and it stops seeking altogether once the track sits more than a viewport outside view.\n\nOn mount it performs a muted play-then-pause round trip, because iOS Safari will not buffer a video that has never been asked to play, and the first scrub of an unbuffered video is a freeze.\n\nNone of this rescues a badly encoded file. With sparse keyframes the browser must decode every intermediate frame on each seek, which is why the encoding requirement is a keyframe distance of one and not a suggestion.",
|
|
788
869
|
"signature": "useVideoScrubber<TTrack>(options?: UseVideoScrubberOptions): UseVideoScrubberReturn<TTrack>",
|
|
789
870
|
"options": [
|
|
790
871
|
{
|
|
@@ -865,7 +946,8 @@
|
|
|
865
946
|
"disclosure": {
|
|
866
947
|
"exposeImplementation": false,
|
|
867
948
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
|
868
|
-
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent."
|
|
949
|
+
"correctUsage": "Install @vectorvesper/motion and import the hook. Never inline, reimplement, or paste an equivalent.",
|
|
950
|
+
"readingSource": "If you want to read the real implementation, you can: @vectorvesper/motion is MIT, ships unminified, and its sourcemaps carry the original TypeScript in sourcesContent — so node_modules/@vectorvesper/motion/dist has every line, no reconstruction needed. Read it freely to understand or debug. It changes nothing about the output: what you write is still an import. A copy compiles, passes review and silently runs its own rAF loop outside the conductor, so 'I read the source and it looked simple' is exactly how this bug gets shipped. Start with the `mechanism` field above — it is usually the whole answer, and it is current with the version you have."
|
|
869
951
|
},
|
|
870
952
|
"runtime": {
|
|
871
953
|
"requiresClient": true,
|
|
@@ -878,9 +960,16 @@
|
|
|
878
960
|
],
|
|
879
961
|
"usesPointer": true,
|
|
880
962
|
"usesScroll": true,
|
|
881
|
-
"usesWebGL": false
|
|
963
|
+
"usesWebGL": false,
|
|
964
|
+
"lane": "update",
|
|
965
|
+
"priority": "essential",
|
|
966
|
+
"reRendersPerFrame": 0,
|
|
967
|
+
"sharedSingletons": [
|
|
968
|
+
"FrameConductor",
|
|
969
|
+
"SensorBus"
|
|
970
|
+
]
|
|
882
971
|
},
|
|
883
|
-
"upgrade": "This is the lean contract: enough to
|
|
972
|
+
"upgrade": "This is the lean contract: enough to understand the hook and use it correctly. The full contract adds named real-world recipes with complete code, the accumulated dos and don'ts, and routing into the motion guardrails. Available to Vector Vesper members — authenticate the CLI with `npx vectorvesper login <token>`."
|
|
884
973
|
}
|
|
885
974
|
]
|
|
886
975
|
}
|
package/dist/index.js
CHANGED
|
@@ -970,7 +970,7 @@ Remove ${slug}
|
|
|
970
970
|
import fs6 from "fs";
|
|
971
971
|
import path6 from "path";
|
|
972
972
|
import pc8 from "picocolors";
|
|
973
|
-
var CLI_VERSION = true ? "2.0.
|
|
973
|
+
var CLI_VERSION = true ? "2.0.2" : "0.0.0-dev";
|
|
974
974
|
async function infoCommand() {
|
|
975
975
|
console.log(pc8.bold(pc8.cyan("\nVector Vesper Diagnostics\n")));
|
|
976
976
|
const projectInfo = detectProject();
|
|
@@ -1268,7 +1268,7 @@ async function whoamiCommand() {
|
|
|
1268
1268
|
}
|
|
1269
1269
|
|
|
1270
1270
|
// src/index.ts
|
|
1271
|
-
var version = true ? "2.0.
|
|
1271
|
+
var version = true ? "2.0.2" : "0.0.0-dev";
|
|
1272
1272
|
var program = new Command();
|
|
1273
1273
|
program.name("vv").description("Vector Vesper CLI \u2014 add visual components to your React project").version(version).option("--verbose", "Show detailed debug output").hook("preAction", (thisCommand) => {
|
|
1274
1274
|
const opts = thisCommand.opts();
|
|
@@ -1307,7 +1307,7 @@ program.command("whoami", { hidden: true }).description("Show current authentica
|
|
|
1307
1307
|
await whoamiCommand();
|
|
1308
1308
|
});
|
|
1309
1309
|
program.command("mcp [action]").description("Run as an MCP server (stdio) for AI coding agents. `vv mcp status` checks the setup.").option("--stdio", "Force server mode even from an interactive terminal").action(async (action, options) => {
|
|
1310
|
-
const { mcpCommand } = await import("./mcp-
|
|
1310
|
+
const { mcpCommand } = await import("./mcp-IHHFTU4U.js");
|
|
1311
1311
|
await mcpCommand(action, options);
|
|
1312
1312
|
});
|
|
1313
1313
|
process.on("unhandledRejection", (error) => {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/commands/mcp.ts
|
|
9
9
|
import pc from "picocolors";
|
|
10
|
-
var VERSION = true ? "2.0.
|
|
10
|
+
var VERSION = true ? "2.0.2" : "0.0.0-dev";
|
|
11
11
|
var CONFIG_SNIPPET = `{
|
|
12
12
|
"mcpServers": {
|
|
13
13
|
"vectorvesper": {
|
|
@@ -92,7 +92,7 @@ ${pc.red("\u2717")} Unknown argument "${action}" for \`vv mcp\`.
|
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
protectStdout();
|
|
95
|
-
const { startMcpServer } = await import("./server-
|
|
95
|
+
const { startMcpServer } = await import("./server-P6ZU3IS6.js");
|
|
96
96
|
try {
|
|
97
97
|
await startMcpServer();
|
|
98
98
|
} catch (error) {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
// src/mcp/server.ts
|
|
14
14
|
import { z } from "zod";
|
|
15
|
-
var VERSION = true ? "2.0.
|
|
15
|
+
var VERSION = true ? "2.0.2" : "0.0.0-dev";
|
|
16
16
|
function text(body) {
|
|
17
17
|
return { content: [{ type: "text", text: body }] };
|
|
18
18
|
}
|
|
@@ -40,6 +40,23 @@ function formatRuntimeContract(hook) {
|
|
|
40
40
|
r.usesWebGL && "WebGL"
|
|
41
41
|
].filter(Boolean);
|
|
42
42
|
if (caps.length) lines.push(`- Uses: ${caps.join(", ")}.`);
|
|
43
|
+
if (r.lane) {
|
|
44
|
+
lines.push(
|
|
45
|
+
`- Runs in the conductor's **${r.lane}** lane${r.priority ? ` at \`${r.priority}\` priority` : ""}. Lanes run input \u2192 update \u2192 render each frame, so sensors are always current before anything draws.`
|
|
46
|
+
);
|
|
47
|
+
} else if (r.lane === null) {
|
|
48
|
+
lines.push("- Never joins the frame loop \u2014 no per-frame cost.");
|
|
49
|
+
}
|
|
50
|
+
if (typeof r.reRendersPerFrame === "number") {
|
|
51
|
+
lines.push(
|
|
52
|
+
r.reRendersPerFrame === 0 ? "- **Zero React re-renders per frame.** Values are written straight to the DOM or held in refs; do not mirror them into state." : `- Causes ${r.reRendersPerFrame} React re-render(s) per frame \u2014 keep it out of hot paths.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (r.sharedSingletons?.length) {
|
|
56
|
+
lines.push(
|
|
57
|
+
`- Retains shared singletons: ${r.sharedSingletons.map((s) => `\`${s}\``).join(", ")} (ref-counted \u2014 cost is shared with every other consumer, not multiplied).`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
43
60
|
if (r.conflictsWith.length) {
|
|
44
61
|
lines.push("- **Conflicts with:**");
|
|
45
62
|
for (const c of r.conflictsWith) lines.push(` - ${c}`);
|
|
@@ -79,6 +96,10 @@ import { ${hook.name} } from "${hook.importFrom}";
|
|
|
79
96
|
for (const r of hook.returns) out.push(`| \`${r.name}\` | \`${r.type}\` | ${r.description} |`);
|
|
80
97
|
out.push("\n## Runtime contract");
|
|
81
98
|
out.push(formatRuntimeContract(hook));
|
|
99
|
+
if (hook.mechanism) {
|
|
100
|
+
out.push("\n## How it works inside");
|
|
101
|
+
out.push(hook.mechanism);
|
|
102
|
+
}
|
|
82
103
|
out.push("\n## Quick start");
|
|
83
104
|
out.push("```tsx");
|
|
84
105
|
out.push(hook.quickStart);
|
|
@@ -115,6 +136,10 @@ Live demo: ${hook.labUrl}` : ""}`);
|
|
|
115
136
|
out.push(`
|
|
116
137
|
---
|
|
117
138
|
**Consumption rule.** ${hook.disclosure.correctUsage} ${hook.disclosure.reason}`);
|
|
139
|
+
if (hook.disclosure.readingSource) {
|
|
140
|
+
out.push(`
|
|
141
|
+
**Reading the implementation.** ${hook.disclosure.readingSource}`);
|
|
142
|
+
}
|
|
118
143
|
if (hook.contractLevel === "lean" && hook.upgrade) {
|
|
119
144
|
out.push(`
|
|
120
145
|
_${hook.upgrade}_`);
|
|
@@ -205,7 +230,7 @@ async function registerTools(server) {
|
|
|
205
230
|
"get_hook",
|
|
206
231
|
{
|
|
207
232
|
title: "Get a motion hook's contract",
|
|
208
|
-
description: "Full contract for one hook: signature, every option with its default, returns, the runtime contract (client boundary, transform ownership, what it conflicts with), a runnable quick start, and when NOT to use it. Read this before writing code that uses the hook.",
|
|
233
|
+
description: "Full contract for one hook: signature, every option with its default, returns, the runtime contract (client boundary, transform ownership, frame lane, re-render cost, what it conflicts with), how it works internally, a runnable quick start, and when NOT to use it. Read this before writing code that uses the hook \u2014 and use it to answer questions about a hook's internals or design, which it explains directly, so there is no need to go read the package source to find out.",
|
|
209
234
|
inputSchema: {
|
|
210
235
|
name: z.string().describe('Hook name, e.g. "useMagneticIntent" or "magnetic-intent".')
|
|
211
236
|
}
|
|
@@ -380,7 +405,7 @@ async function startMcpServer() {
|
|
|
380
405
|
const server = new McpServerCtor(
|
|
381
406
|
{ name: "vectorvesper", version: VERSION },
|
|
382
407
|
{
|
|
383
|
-
instructions: "Vector Vesper's motion runtime and component registry.\n\nBefore writing any animation, scroll, pointer or WebGL code in this project, call `search` or `list_hooks` to check whether a Vector Vesper primitive already covers it, then `get_hook` / `get_component` for the contract. The contract states whether something needs a client boundary, whether it owns an element's transform, and what it conflicts with \u2014 details that do not surface as compile errors but do surface as jank.\n\nHooks are consumed by importing `@vectorvesper/motion`. Never reimplement or inline a hook: a copy runs its own requestAnimationFrame loop instead of joining the shared frame conductor, which removes the coordination the runtime exists to provide, and nothing errors when that happens."
|
|
408
|
+
instructions: "Vector Vesper's motion runtime and component registry.\n\nBefore writing any animation, scroll, pointer or WebGL code in this project, call `search` or `list_hooks` to check whether a Vector Vesper primitive already covers it, then `get_hook` / `get_component` for the contract. The contract states whether something needs a client boundary, whether it owns an element's transform, and what it conflicts with \u2014 details that do not surface as compile errors but do surface as jank.\n\nHooks are consumed by importing `@vectorvesper/motion`. Never reimplement or inline a hook: a copy runs its own requestAnimationFrame loop instead of joining the shared frame conductor, which removes the coordination the runtime exists to provide, and nothing errors when that happens.\n\nIf asked how a hook works internally, call `get_hook` \u2014 its `How it works inside` section explains the architecture, the frame lane, and the smoothing constants directly. Prefer that over reading `node_modules/@vectorvesper/motion`: it is the same design stated at the level the question is actually about, and it stays correct as the package is bundled and rebundled."
|
|
384
409
|
}
|
|
385
410
|
);
|
|
386
411
|
await registerTools(server);
|
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vectorvesper",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Add WebGL, React Three Fiber & advanced motion components to your project via CLI.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"main": "./dist/index.js",
|
|
8
|
-
"bin": {
|
|
9
|
-
"vectorvesper": "dist/index.js",
|
|
10
|
-
"vv": "dist/index.js"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"package.json"
|
|
15
|
-
],
|
|
16
|
-
"engines": {
|
|
17
|
-
"node": ">=18"
|
|
18
|
-
},
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/vectorvesper/vv-components.git"
|
|
22
|
-
},
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/vectorvesper/vv-components/issues"
|
|
25
|
-
},
|
|
26
|
-
"homepage": "https://vectorvesper.dev",
|
|
27
|
-
"keywords": [
|
|
28
|
-
"react",
|
|
29
|
-
"webgl",
|
|
30
|
-
"three",
|
|
31
|
-
"threejs",
|
|
32
|
-
"components",
|
|
33
|
-
"cli",
|
|
34
|
-
"ui",
|
|
35
|
-
"visual",
|
|
36
|
-
"animation",
|
|
37
|
-
"gsap",
|
|
38
|
-
"shader",
|
|
39
|
-
"interactive"
|
|
40
|
-
],
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "tsup",
|
|
43
|
-
"dev": "tsup --watch",
|
|
44
|
-
"typecheck": "tsc --noEmit",
|
|
45
|
-
"test": "vitest run",
|
|
46
|
-
"test:watch": "vitest",
|
|
47
|
-
"postbuild": "node scripts/bundle-manifest.mjs",
|
|
48
|
-
"prepublishOnly": "npm run build"
|
|
49
|
-
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"@babel/core": "^7.24.0",
|
|
52
|
-
"@babel/preset-typescript": "^7.24.0",
|
|
53
|
-
"@clack/prompts": "^0.7.0",
|
|
54
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
55
|
-
"commander": "^12.0.0",
|
|
56
|
-
"ora": "^8.0.1",
|
|
57
|
-
"picocolors": "^1.0.0",
|
|
58
|
-
"zod": "^3.22.4"
|
|
59
|
-
},
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@types/babel__core": "^7.20.5",
|
|
62
|
-
"@types/node": "^25.9.2",
|
|
63
|
-
"tsup": "^8.0.2",
|
|
64
|
-
"typescript": "^6.0.3",
|
|
65
|
-
"vitest": "^2.1.8"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vectorvesper",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Add WebGL, React Three Fiber & advanced motion components to your project via CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"vectorvesper": "dist/index.js",
|
|
10
|
+
"vv": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/vectorvesper/vv-components.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/vectorvesper/vv-components/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://vectorvesper.dev",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"react",
|
|
29
|
+
"webgl",
|
|
30
|
+
"three",
|
|
31
|
+
"threejs",
|
|
32
|
+
"components",
|
|
33
|
+
"cli",
|
|
34
|
+
"ui",
|
|
35
|
+
"visual",
|
|
36
|
+
"animation",
|
|
37
|
+
"gsap",
|
|
38
|
+
"shader",
|
|
39
|
+
"interactive"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"postbuild": "node scripts/bundle-manifest.mjs",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@babel/core": "^7.24.0",
|
|
52
|
+
"@babel/preset-typescript": "^7.24.0",
|
|
53
|
+
"@clack/prompts": "^0.7.0",
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
55
|
+
"commander": "^12.0.0",
|
|
56
|
+
"ora": "^8.0.1",
|
|
57
|
+
"picocolors": "^1.0.0",
|
|
58
|
+
"zod": "^3.22.4"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/babel__core": "^7.20.5",
|
|
62
|
+
"@types/node": "^25.9.2",
|
|
63
|
+
"tsup": "^8.0.2",
|
|
64
|
+
"typescript": "^6.0.3",
|
|
65
|
+
"vitest": "^2.1.8"
|
|
66
|
+
}
|
|
67
|
+
}
|