@ape-egg/vibe 1.3.2 → 1.6.1

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.
@@ -0,0 +1,153 @@
1
+ /**
2
+ * pre-compiled-iterations.js
3
+ *
4
+ * Pre-compiled iteration rendering - production version of iteration optimization.
5
+ * Uses pre-compiled batch functions from manifest generated by the compiler.
6
+ *
7
+ * This is the production implementation that runs compiled code generated at build time.
8
+ * Based on the prototype in _vibe-compiled-iteration-batch.js
9
+ */
10
+
11
+ // Reusable template element for parsing compiled HTML
12
+ const parseTemplate = typeof document !== 'undefined' ? document.createElement('template') : null;
13
+
14
+ /**
15
+ * Check if template has nested iterations or conditionals
16
+ */
17
+ const hasNestedStructures = (template) => {
18
+ if (!template || !template.children) return false;
19
+ for (const key in template.children) {
20
+ const child = template.children[key];
21
+ if (!child) continue;
22
+ if (child.type === 'iteration' || child.type === 'conditional') return true;
23
+ if (hasNestedStructures(child)) return true;
24
+ }
25
+ return false;
26
+ };
27
+
28
+ /**
29
+ * Check if iteration node has compiled batch function
30
+ */
31
+ export const canUseCompiled = (iterationNode) => {
32
+ if (!iterationNode || !iterationNode.meta) {
33
+ return false;
34
+ }
35
+
36
+ // Check compiled data from manifest merge
37
+ const compiled = iterationNode.compiled;
38
+ if (!compiled || !compiled.iterations || !compiled.iterations.batchFn) {
39
+ return false;
40
+ }
41
+
42
+ // If we have a compiled batch function, we can use it even if the template
43
+ // has nested structures, because the compiler has already inlined them
44
+ // into the batch function
45
+ return true;
46
+ };
47
+
48
+ /**
49
+ * Get compiled function metadata from iteration node
50
+ */
51
+ export const getCompiledMeta = (iterationNode) => {
52
+ const compiled = iterationNode.compiled;
53
+ if (!compiled || !compiled.iterations) return null;
54
+
55
+ return {
56
+ batchFn: compiled.iterations.batchFn,
57
+ itemAlias: compiled.iterations.itemAlias,
58
+ indexAlias: compiled.iterations.indexAlias,
59
+ };
60
+ };
61
+
62
+ /**
63
+ * Render iteration using pre-compiled batch function from manifest
64
+ */
65
+ export const renderCompiled = (iterationNode, array, state, compiledMeta, parent, endComment) => {
66
+ // Create function from string if not cached
67
+ if (!iterationNode.runtime.compiledFn) {
68
+ try {
69
+ // compiledMeta.batchFn is a complete arrow function: (arr, $) => { ... }
70
+ // Wrap in a function that returns it, then call to get the actual function
71
+ iterationNode.runtime.compiledFn = new Function('return ' + compiledMeta.batchFn)();
72
+ } catch (e) {
73
+ console.error('[compiled-iteration] Failed to create compiled function:', e);
74
+ return false; // Signal failure
75
+ }
76
+ }
77
+
78
+ // Build HTML using compiled function
79
+ const html = iterationNode.runtime.compiledFn(array, state);
80
+
81
+ // Parse and insert
82
+ if (parseTemplate) {
83
+ parseTemplate.innerHTML = html;
84
+ const frag = parseTemplate.content;
85
+ const kids = frag.children;
86
+
87
+ // Track instances - pre-allocate array for performance
88
+ const arrayLen = array.length;
89
+ const instances = new Array(arrayLen);
90
+ for (let i = 0; i < arrayLen; i++) {
91
+ instances[i] = { element: kids[i], item: array[i], index: i };
92
+ }
93
+
94
+ parent.insertBefore(frag, endComment);
95
+ iterationNode.runtime.instances = instances;
96
+ return true;
97
+ }
98
+
99
+ return false;
100
+ };
101
+
102
+ /**
103
+ * Update iteration using pre-compiled batch function (bulk rebuild)
104
+ */
105
+ export const updateCompiled = (iterationNode, newArray, state, compiledMeta, startComment, endComment) => {
106
+ const parent = startComment.parentNode;
107
+
108
+ // Clear existing instances
109
+ if (iterationNode.runtime.instances.length > 0) {
110
+ const range = document.createRange();
111
+ range.setStartAfter(startComment);
112
+ range.setEndBefore(endComment);
113
+ range.deleteContents();
114
+ }
115
+
116
+ if (newArray.length === 0) {
117
+ iterationNode.runtime.instances = [];
118
+ return true;
119
+ }
120
+
121
+ // Create function if not cached
122
+ if (!iterationNode.runtime.compiledFn) {
123
+ try {
124
+ iterationNode.runtime.compiledFn = new Function('return ' + compiledMeta.batchFn)();
125
+ } catch (e) {
126
+ console.error('Failed to create compiled function:', e);
127
+ return false;
128
+ }
129
+ }
130
+
131
+ // Build HTML using compiled function
132
+ const html = iterationNode.runtime.compiledFn(newArray, state);
133
+
134
+ // Parse and insert
135
+ if (parseTemplate) {
136
+ parseTemplate.innerHTML = html;
137
+ const frag = parseTemplate.content;
138
+ const kids = frag.children;
139
+
140
+ // Track instances - pre-allocate array for performance
141
+ const arrayLen = newArray.length;
142
+ const instances = new Array(arrayLen);
143
+ for (let i = 0; i < arrayLen; i++) {
144
+ instances[i] = { element: kids[i], item: newArray[i], index: i };
145
+ }
146
+
147
+ parent.insertBefore(frag, endComment);
148
+ iterationNode.runtime.instances = instances;
149
+ return true;
150
+ }
151
+
152
+ return false;
153
+ };
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Hyperspeed - Pre-compiled manifest support for Vibe
2
+ * Pre-compiled Manifest - Manifest support for pre-compiled Vibe pages
3
3
  *
4
- * Handles detection and restoration of pre-compiled pages.
4
+ * Handles detection, loading, and restoration of pre-compiled page manifests.
5
5
  */
6
6
 
7
- // Build hyperspeed manifest with restoration data (captures markers before hydration)
7
+ // Build manifest with restoration data (captures markers before hydration)
8
8
  export const buildHyperspeedManifest = (parsedTree) => {
9
9
  // Helper to split text content by @[...] markers into array
10
10
  const splitByMarkers = (text) => {
@@ -121,20 +121,35 @@ export const buildHyperspeedManifest = (parsedTree) => {
121
121
  };
122
122
  };
123
123
 
124
- // Hyperspeed detection - per-page manifest
124
+ // Manifest detection - per-page manifest
125
125
  // Each compiled page has its own manifest: /vibe-hyperspeed/{page-path}.manifest.js
126
- let hyperspeedManifest = null;
126
+ let hyperspeedData = null;
127
127
  let hyperspeedDetectionAttempted = false;
128
128
 
129
129
  /**
130
130
  * Detect page-specific manifest (async, cached after first call)
131
+ * Returns { manifest, path } or null
131
132
  */
132
133
  const detectHyperspeed = async () => {
133
- if (hyperspeedDetectionAttempted) return hyperspeedManifest;
134
+ if (hyperspeedDetectionAttempted) return hyperspeedData;
134
135
  hyperspeedDetectionAttempted = true;
135
136
 
136
137
  try {
137
- const pagePath = window.location.pathname;
138
+ let pagePath = window.location.pathname;
139
+
140
+ // Normalize path: handle directory URLs and missing extensions
141
+ if (pagePath.endsWith('/')) {
142
+ // /compiled/ -> /compiled/index.html
143
+ pagePath = pagePath + 'index.html';
144
+ } else if (!pagePath.includes('.')) {
145
+ // /compiled/mypage -> /compiled/mypage.html
146
+ const lastSlash = pagePath.lastIndexOf('/');
147
+ const lastSegment = pagePath.substring(lastSlash + 1);
148
+ if (lastSegment && !lastSegment.includes('.')) {
149
+ pagePath = pagePath + '.html';
150
+ }
151
+ }
152
+
138
153
  const pathSegments = pagePath.split('/').filter(s => s);
139
154
 
140
155
  if (pathSegments.length === 0) return null;
@@ -170,9 +185,12 @@ const detectHyperspeed = async () => {
170
185
  for (const manifestPath of possiblePaths) {
171
186
  try {
172
187
  const module = await import(manifestPath);
173
- hyperspeedManifest = module.default;
174
- return hyperspeedManifest;
175
- } catch {
188
+ hyperspeedData = {
189
+ manifest: module.default,
190
+ path: manifestPath
191
+ };
192
+ return hyperspeedData;
193
+ } catch (e) {
176
194
  // Try next path
177
195
  continue;
178
196
  }
@@ -187,7 +205,7 @@ const detectHyperspeed = async () => {
187
205
  };
188
206
 
189
207
  /**
190
- * Restore DOM from pre-rendered values to @[...] markers using hyperspeed manifest
208
+ * Restore DOM from pre-rendered values to @[...] markers using pre-compiled manifest
191
209
  * This enables FOUC-free loading while maintaining runtime reactivity
192
210
  *
193
211
  * Flow:
@@ -203,8 +221,8 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
203
221
  const walkTree = (tree, element) => {
204
222
  if (!tree || !element) return;
205
223
 
206
- // Check for hyperspeed restoration data
207
- const restoration = tree.hyperspeedRestoration;
224
+ // Check for compiled restoration data
225
+ const restoration = tree.compiled?.restoration;
208
226
 
209
227
  if (restoration) {
210
228
  // Restore text content with markers if parsed contains bindings
@@ -230,95 +248,94 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
230
248
  }
231
249
  }
232
250
 
233
- // Skip attribute restoration - manifest merge provides all binding metadata
234
- // Attributes are already pre-rendered correctly by compiler
235
- // restoration.attributes exists only for manifest merge, not for DOM restoration
236
-
237
- // Restore conditionals
238
- if ((restoration.trueTemplate || restoration.falseTemplate) && tree.type === 'conditional') {
239
- // Find comment markers
240
- const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
241
- let startComment = null;
242
- let elseComment = null;
243
- let endComment = null;
244
-
245
- while (walker.nextNode()) {
246
- const comment = walker.currentNode;
247
- const text = comment.textContent.trim();
248
- if (text.startsWith('if')) {
249
- startComment = comment;
250
- } else if (text === 'else') {
251
- elseComment = comment;
252
- } else if (text === '/if') {
253
- endComment = comment;
254
- break;
255
- }
251
+ // Restore attribute bindings for reactivity
252
+ // Two cases:
253
+ // 1. Boolean-like attributes with falsy values were removed - restore them
254
+ // 2. Value attributes were stamped - replace stamped values with markers
255
+ if (restoration.attributes) {
256
+ for (const [attrName, attrValue] of Object.entries(restoration.attributes)) {
257
+ // Always set the attribute to restore the marker
258
+ // - If missing (boolean-like, falsy): adds it back
259
+ // - If present (value attr, stamped): replaces stamped value with marker
260
+ element.setAttribute(attrName, attrValue);
256
261
  }
262
+ }
257
263
 
258
- if (startComment && endComment) {
259
- // Remove pre-rendered content in true branch
260
- let current = startComment.nextSibling;
261
- while (current && current !== (elseComment || endComment)) {
262
- const next = current.nextSibling;
263
- if (current.nodeType === Node.ELEMENT_NODE) {
264
- current.remove();
265
- }
266
- current = next;
267
- }
264
+ // Restore name bindings (sparse array indexed by attribute position)
265
+ if (restoration.nameBindings && Array.isArray(restoration.nameBindings)) {
266
+ const attrs = Array.from(element.attributes);
268
267
 
269
- // Remove pre-rendered content in false branch if exists
270
- if (elseComment) {
271
- current = elseComment.nextSibling;
272
- while (current && current !== endComment) {
273
- const next = current.nextSibling;
274
- if (current.nodeType === Node.ELEMENT_NODE) {
275
- current.remove();
276
- }
277
- current = next;
278
- }
268
+ for (let i = 0; i < restoration.nameBindings.length; i++) {
269
+ const marker = restoration.nameBindings[i];
270
+ if (marker && attrs[i]) {
271
+ // Replace the stamped attribute name with the marker
272
+ const stampedName = attrs[i].name;
273
+ const attrValue = attrs[i].value;
274
+
275
+ element.removeAttribute(stampedName);
276
+ element.setAttribute(marker, attrValue);
279
277
  }
278
+ }
279
+ }
280
+
281
+ // After restoration, delete compiled.restoration (runtime will parse DOM fresh)
282
+ if (tree.compiled) {
283
+ delete tree.compiled.restoration;
284
+ }
285
+ }
286
+
287
+ // IMPORTANT: Restore text nodes BEFORE processing conditionals/iterations
288
+ // Text nodes use childNodes indices which become invalid after DOM modifications
289
+ if (tree.children) {
290
+ for (const key in tree.children) {
291
+ if (key.startsWith('text_')) {
292
+ const childTree = tree.children[key];
293
+ const restoration = childTree.compiled?.restoration;
294
+
295
+ if (restoration?.parsed && Array.isArray(restoration.parsed)) {
296
+ const hasBindings = restoration.parsed.some(item =>
297
+ typeof item === 'string' && item.includes('@[')
298
+ );
280
299
 
281
- // Insert templates (runtime will process them)
282
- if (restoration.trueTemplate) {
283
- const template = document.createElement('div');
284
- template.innerHTML = restoration.trueTemplate;
285
- const templateNode = template.firstElementChild; // Use firstElementChild, not firstChild
286
- if (templateNode) {
287
- (elseComment || endComment).parentNode.insertBefore(
288
- templateNode,
289
- elseComment || endComment
290
- );
300
+ if (hasBindings) {
301
+ const originalContent = restoration.parsed.join('');
302
+
303
+ // Extract index from key (e.g., text_0 -> 0)
304
+ const match = key.match(/_(\d+)$/);
305
+ if (match) {
306
+ const index = parseInt(match[1], 10);
307
+
308
+ if (index < element.childNodes.length) {
309
+ const textNode = element.childNodes[index];
310
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
311
+ textNode.textContent = originalContent;
312
+ }
313
+ }
314
+ }
291
315
  }
292
316
  }
293
317
 
294
- if (restoration.falseTemplate && elseComment) {
295
- const template = document.createElement('div');
296
- template.innerHTML = restoration.falseTemplate;
297
- const templateNode = template.firstElementChild; // Use firstElementChild, not firstChild
298
- if (templateNode) {
299
- endComment.parentNode.insertBefore(templateNode, endComment);
300
- }
318
+ if (childTree.compiled) {
319
+ delete childTree.compiled.restoration;
301
320
  }
302
321
  }
303
322
  }
304
-
305
- // After restoration, delete hyperspeedRestoration (runtime will parse DOM fresh)
306
- delete tree.hyperspeedRestoration;
307
323
  }
308
324
 
309
- // Recursively restore children
325
+ // Handle conditional and iteration children - process them at THIS level before recursing
326
+ // These are comment nodes in the DOM, not elements, so we need to find them here
310
327
  if (tree.children) {
311
328
  for (const key in tree.children) {
312
329
  const childTree = tree.children[key];
313
330
 
314
- // Handle iteration nodes with restoration data
315
- if (childTree.type === 'iteration' && childTree.hyperspeedRestoration) {
316
- const restoration = childTree.hyperspeedRestoration;
331
+ // Handle iteration restoration
332
+ if (childTree.type === 'iteration' && childTree.compiled?.restoration) {
333
+ const restoration = childTree.compiled.restoration;
317
334
 
318
- // Find iteration comment by searching (not using index)
319
- // This avoids index shifting issues after restoration changes DOM
335
+ // Find iteration comment by matching the expression
320
336
  let startComment = null;
321
337
  let endComment = null;
338
+ let depth = 0;
322
339
 
323
340
  const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
324
341
  while (walker.nextNode()) {
@@ -328,11 +345,21 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
328
345
  // Skip already processed iterations
329
346
  if (comment._vibeProcessed) continue;
330
347
 
331
- if (trimmed.startsWith('each ') && !startComment) {
348
+ // Match by expression: "each items as item, i"
349
+ const expectedComment = `each ${restoration.expression}`;
350
+ if (trimmed === expectedComment && !startComment) {
332
351
  startComment = comment;
333
- } else if (trimmed === '/each' && startComment && !endComment) {
334
- endComment = comment;
335
- break;
352
+ depth = 1;
353
+ } else if (startComment) {
354
+ if (trimmed.startsWith('each ')) {
355
+ depth++;
356
+ } else if (trimmed === '/each') {
357
+ depth--;
358
+ if (depth === 0) {
359
+ endComment = comment;
360
+ break;
361
+ }
362
+ }
336
363
  }
337
364
  }
338
365
 
@@ -341,76 +368,134 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
341
368
  startComment._vibeProcessed = true;
342
369
  endComment._vibeProcessed = true;
343
370
 
344
- // Remove all pre-rendered items between comments
371
+ const parent = startComment.parentNode;
372
+ const insertionPoint = endComment.nextSibling;
373
+
374
+ // Remove all pre-rendered content between comments
345
375
  let current = startComment.nextSibling;
346
376
  while (current && current !== endComment) {
347
377
  const next = current.nextSibling;
348
- if (current.nodeType === Node.ELEMENT_NODE) {
378
+ if (current.nodeType !== Node.COMMENT_NODE) {
349
379
  current.remove();
350
380
  }
351
381
  current = next;
352
382
  }
353
383
 
354
- // Insert template from restoration data
355
- const temp = document.createElement('div');
356
- temp.innerHTML = restoration.template;
357
- const templateNode = temp.firstElementChild;
358
- if (templateNode) {
359
- endComment.parentNode.insertBefore(templateNode, endComment);
384
+ // Insert the template (single item)
385
+ const tempContainer = document.createElement('div');
386
+ tempContainer.innerHTML = restoration.template;
387
+
388
+ const fragment = document.createDocumentFragment();
389
+ while (tempContainer.firstChild) {
390
+ fragment.appendChild(tempContainer.firstChild);
360
391
  }
392
+ parent.insertBefore(fragment, endComment);
361
393
  }
362
394
 
363
- delete childTree.hyperspeedRestoration;
395
+ // Delete the iteration node from tree - runtime will re-create it
396
+ delete tree.children[key];
364
397
  continue;
365
398
  }
366
399
 
367
- // Handle conditional nodes (similar approach)
368
- if (childTree.type === 'conditional') {
369
- // TODO: Implement conditional restoration when needed
370
- continue;
371
- }
400
+ if (childTree.type === 'conditional' && childTree.compiled?.restoration) {
401
+ const restoration = childTree.compiled.restoration;
372
402
 
373
- // Handle text nodes with restoration data
374
- if (key.startsWith('text_') && childTree.hyperspeedRestoration) {
375
- const restoration = childTree.hyperspeedRestoration;
376
- if (restoration.parsed && Array.isArray(restoration.parsed)) {
377
- const hasBindings = restoration.parsed.some(item =>
378
- typeof item === 'string' && item.includes('@[')
379
- );
403
+ // Find conditional comment markers in the current element
404
+ // But SKIP conditionals that are inside iteration blocks
405
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
406
+ let startComment = null;
407
+ let endComment = null;
408
+ let insideIteration = false;
380
409
 
381
- if (hasBindings) {
382
- const originalContent = restoration.parsed.join('');
383
- if (element.childNodes.length === 1 && element.childNodes[0].nodeType === 3) {
384
- element.childNodes[0].textContent = originalContent;
385
- } else if (element.childNodes.length === 0) {
386
- element.textContent = originalContent;
387
- }
410
+ while (walker.nextNode()) {
411
+ const comment = walker.currentNode;
412
+ const text = comment.textContent.trim();
413
+
414
+ // Track if we're inside an iteration block
415
+ if (text.startsWith('each ')) {
416
+ insideIteration = true;
417
+ continue;
418
+ } else if (text === '/each') {
419
+ insideIteration = false;
420
+ continue;
421
+ }
422
+
423
+ // Skip conditionals inside iterations - runtime will handle them
424
+ if (insideIteration) continue;
425
+
426
+ // Skip already processed conditionals
427
+ if (comment._vibeProcessed) continue;
428
+
429
+ if (text.startsWith('if') && !startComment) {
430
+ startComment = comment;
431
+ } else if (text === '/if' && startComment && !endComment) {
432
+ endComment = comment;
433
+ break;
434
+ }
435
+ }
436
+
437
+ if (startComment && endComment) {
438
+ // Mark as processed
439
+ startComment._vibeProcessed = true;
440
+ endComment._vibeProcessed = true;
441
+
442
+ // Remove ALL pre-rendered content between start and end comments
443
+ // This includes the <!-- else --> marker from compiled HTML
444
+ let current = startComment.nextSibling;
445
+ while (current && current !== endComment) {
446
+ const next = current.nextSibling;
447
+ current.remove(); // Remove ALL nodes, including comment nodes
448
+ current = next;
388
449
  }
450
+
451
+ // Insert the template content BEFORE endComment
452
+ const tempContainer = document.createElement('div');
453
+ tempContainer.innerHTML = restoration.template;
454
+
455
+ const fragment = document.createDocumentFragment();
456
+ while (tempContainer.firstChild) {
457
+ fragment.appendChild(tempContainer.firstChild);
458
+ }
459
+ endComment.parentNode.insertBefore(fragment, endComment);
389
460
  }
390
- delete childTree.hyperspeedRestoration;
461
+
462
+ // Delete restoration data AND remove the conditional node from tree
463
+ // Runtime will re-create it from the restored DOM
464
+ delete tree.children[key];
465
+ }
466
+ }
467
+ }
468
+
469
+ // Recursively restore children (skip conditional/iteration children as they were already handled)
470
+ if (tree.children) {
471
+ for (const key in tree.children) {
472
+ const childTree = tree.children[key];
473
+
474
+ // Skip conditionals and iterations (already processed/deleted above)
475
+ if (childTree.type === 'conditional' || childTree.type === 'iteration') {
391
476
  continue;
392
477
  }
393
478
 
394
- // Find corresponding child element
395
- // Keys like "strong_0" use childNodes indices (includes text nodes/comments)
396
- const match = key.match(/_(\d+)$/);
479
+ // Skip text nodes - already handled before conditional/iteration processing
480
+ if (key.startsWith('text_')) {
481
+ continue;
482
+ }
483
+
484
+ // Find corresponding child element by tag name and index
485
+ // Keys like "layout_1" mean the node at childNodes index 1 (includes text nodes)
486
+ // NOT the second layout element
487
+ // Tag names can contain digits (h1, h2, etc.) so use [a-z0-9-]+
488
+ const match = key.match(/^([a-z0-9-]+)_(\d+)$/);
397
489
  if (match) {
398
- const index = parseInt(match[1], 10);
399
- const childNode = element.childNodes[index]; // Use childNodes (all nodes)
490
+ const tagName = match[1].toUpperCase();
491
+ const nodeIndex = parseInt(match[2], 10);
400
492
 
401
- // Only walk if it's an element node
402
- if (childNode && childNode.nodeType === Node.ELEMENT_NODE) {
493
+ // Get the node at this childNodes index
494
+ const childNode = element.childNodes[nodeIndex];
495
+
496
+ if (childNode && childNode.nodeType === Node.ELEMENT_NODE && childNode.nodeName === tagName) {
403
497
  walkTree(childTree, childNode);
404
498
  }
405
- } else {
406
- // Try to find by tag name for non-indexed keys
407
- const tagName = key.split('_')[0].toUpperCase();
408
- const childElement = Array.from(element.children).find(
409
- child => child.nodeName === tagName
410
- );
411
- if (childElement) {
412
- walkTree(childTree, childElement);
413
- }
414
499
  }
415
500
  }
416
501
  }
@@ -420,6 +505,7 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
420
505
  };
421
506
 
422
507
  // Top-level await to detect before module exports
423
- hyperspeedManifest = await detectHyperspeed();
508
+ hyperspeedData = await detectHyperspeed();
424
509
 
425
- export { hyperspeedManifest };
510
+ export const hyperspeedManifest = hyperspeedData?.manifest || null;
511
+ export const hyperspeedPath = hyperspeedData?.path || null;
package/runtime/utils.js CHANGED
@@ -67,7 +67,8 @@ export const resolveThisPath = (path, element) => {
67
67
 
68
68
  const componentId = findComponentIdForElement(element);
69
69
  if (componentId) {
70
- return path.replace(/^this\./, `${componentId}.`);
70
+ const resolved = path.replace(/^this\./, `${componentId}.`);
71
+ return resolved;
71
72
  }
72
73
 
73
74
  return path;
@@ -0,0 +1,4 @@
1
+ {
2
+ "status": "failed",
3
+ "failedTests": []
4
+ }
package/vibe.css CHANGED
@@ -11,3 +11,22 @@
11
11
  .vibe-fouc * {
12
12
  transition: none !important;
13
13
  }
14
+
15
+ /* Vibe Dehydrate - Skip Reactive Processing
16
+ * Elements with [vibe-dehydrate] attribute or .vibe-dehydrate class are skipped during
17
+ * Vibe's parsing and hydration. Useful for displaying literal @[variable] syntax in
18
+ * documentation, examples, or code snippets without triggering reactivity.
19
+ */
20
+ [vibe-dehydrate],
21
+ .vibe-dehydrate {
22
+ /* No styles applied - marker only */
23
+ }
24
+
25
+ /* Component Wrappers - Layout Transparent
26
+ * Component wrappers (<component> or <div class="component">) use display: contents
27
+ * to make the wrapper invisible in the layout. Children render as if the wrapper doesn't exist.
28
+ */
29
+ component,
30
+ div.component {
31
+ display: contents;
32
+ }