@ape-egg/vibe 1.3.2 → 1.6.0

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,16 +121,17 @@ 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 {
@@ -170,9 +171,12 @@ const detectHyperspeed = async () => {
170
171
  for (const manifestPath of possiblePaths) {
171
172
  try {
172
173
  const module = await import(manifestPath);
173
- hyperspeedManifest = module.default;
174
- return hyperspeedManifest;
175
- } catch {
174
+ hyperspeedData = {
175
+ manifest: module.default,
176
+ path: manifestPath
177
+ };
178
+ return hyperspeedData;
179
+ } catch (e) {
176
180
  // Try next path
177
181
  continue;
178
182
  }
@@ -187,7 +191,7 @@ const detectHyperspeed = async () => {
187
191
  };
188
192
 
189
193
  /**
190
- * Restore DOM from pre-rendered values to @[...] markers using hyperspeed manifest
194
+ * Restore DOM from pre-rendered values to @[...] markers using pre-compiled manifest
191
195
  * This enables FOUC-free loading while maintaining runtime reactivity
192
196
  *
193
197
  * Flow:
@@ -203,8 +207,8 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
203
207
  const walkTree = (tree, element) => {
204
208
  if (!tree || !element) return;
205
209
 
206
- // Check for hyperspeed restoration data
207
- const restoration = tree.hyperspeedRestoration;
210
+ // Check for compiled restoration data
211
+ const restoration = tree.compiled?.restoration;
208
212
 
209
213
  if (restoration) {
210
214
  // Restore text content with markers if parsed contains bindings
@@ -230,95 +234,94 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
230
234
  }
231
235
  }
232
236
 
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
- }
237
+ // Restore attribute bindings for reactivity
238
+ // Two cases:
239
+ // 1. Boolean-like attributes with falsy values were removed - restore them
240
+ // 2. Value attributes were stamped - replace stamped values with markers
241
+ if (restoration.attributes) {
242
+ for (const [attrName, attrValue] of Object.entries(restoration.attributes)) {
243
+ // Always set the attribute to restore the marker
244
+ // - If missing (boolean-like, falsy): adds it back
245
+ // - If present (value attr, stamped): replaces stamped value with marker
246
+ element.setAttribute(attrName, attrValue);
256
247
  }
248
+ }
257
249
 
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
- }
250
+ // Restore name bindings (sparse array indexed by attribute position)
251
+ if (restoration.nameBindings && Array.isArray(restoration.nameBindings)) {
252
+ const attrs = Array.from(element.attributes);
268
253
 
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
- }
254
+ for (let i = 0; i < restoration.nameBindings.length; i++) {
255
+ const marker = restoration.nameBindings[i];
256
+ if (marker && attrs[i]) {
257
+ // Replace the stamped attribute name with the marker
258
+ const stampedName = attrs[i].name;
259
+ const attrValue = attrs[i].value;
260
+
261
+ element.removeAttribute(stampedName);
262
+ element.setAttribute(marker, attrValue);
279
263
  }
264
+ }
265
+ }
266
+
267
+ // After restoration, delete compiled.restoration (runtime will parse DOM fresh)
268
+ if (tree.compiled) {
269
+ delete tree.compiled.restoration;
270
+ }
271
+ }
280
272
 
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
- );
273
+ // IMPORTANT: Restore text nodes BEFORE processing conditionals/iterations
274
+ // Text nodes use childNodes indices which become invalid after DOM modifications
275
+ if (tree.children) {
276
+ for (const key in tree.children) {
277
+ if (key.startsWith('text_')) {
278
+ const childTree = tree.children[key];
279
+ const restoration = childTree.compiled?.restoration;
280
+
281
+ if (restoration?.parsed && Array.isArray(restoration.parsed)) {
282
+ const hasBindings = restoration.parsed.some(item =>
283
+ typeof item === 'string' && item.includes('@[')
284
+ );
285
+
286
+ if (hasBindings) {
287
+ const originalContent = restoration.parsed.join('');
288
+
289
+ // Extract index from key (e.g., text_0 -> 0)
290
+ const match = key.match(/_(\d+)$/);
291
+ if (match) {
292
+ const index = parseInt(match[1], 10);
293
+
294
+ if (index < element.childNodes.length) {
295
+ const textNode = element.childNodes[index];
296
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
297
+ textNode.textContent = originalContent;
298
+ }
299
+ }
300
+ }
291
301
  }
292
302
  }
293
303
 
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
- }
304
+ if (childTree.compiled) {
305
+ delete childTree.compiled.restoration;
301
306
  }
302
307
  }
303
308
  }
304
-
305
- // After restoration, delete hyperspeedRestoration (runtime will parse DOM fresh)
306
- delete tree.hyperspeedRestoration;
307
309
  }
308
310
 
309
- // Recursively restore children
311
+ // Handle conditional and iteration children - process them at THIS level before recursing
312
+ // These are comment nodes in the DOM, not elements, so we need to find them here
310
313
  if (tree.children) {
311
314
  for (const key in tree.children) {
312
315
  const childTree = tree.children[key];
313
316
 
314
- // Handle iteration nodes with restoration data
315
- if (childTree.type === 'iteration' && childTree.hyperspeedRestoration) {
316
- const restoration = childTree.hyperspeedRestoration;
317
+ // Handle iteration restoration
318
+ if (childTree.type === 'iteration' && childTree.compiled?.restoration) {
319
+ const restoration = childTree.compiled.restoration;
317
320
 
318
- // Find iteration comment by searching (not using index)
319
- // This avoids index shifting issues after restoration changes DOM
321
+ // Find iteration comment by matching the expression
320
322
  let startComment = null;
321
323
  let endComment = null;
324
+ let depth = 0;
322
325
 
323
326
  const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
324
327
  while (walker.nextNode()) {
@@ -328,11 +331,21 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
328
331
  // Skip already processed iterations
329
332
  if (comment._vibeProcessed) continue;
330
333
 
331
- if (trimmed.startsWith('each ') && !startComment) {
334
+ // Match by expression: "each items as item, i"
335
+ const expectedComment = `each ${restoration.expression}`;
336
+ if (trimmed === expectedComment && !startComment) {
332
337
  startComment = comment;
333
- } else if (trimmed === '/each' && startComment && !endComment) {
334
- endComment = comment;
335
- break;
338
+ depth = 1;
339
+ } else if (startComment) {
340
+ if (trimmed.startsWith('each ')) {
341
+ depth++;
342
+ } else if (trimmed === '/each') {
343
+ depth--;
344
+ if (depth === 0) {
345
+ endComment = comment;
346
+ break;
347
+ }
348
+ }
336
349
  }
337
350
  }
338
351
 
@@ -341,76 +354,134 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
341
354
  startComment._vibeProcessed = true;
342
355
  endComment._vibeProcessed = true;
343
356
 
344
- // Remove all pre-rendered items between comments
357
+ const parent = startComment.parentNode;
358
+ const insertionPoint = endComment.nextSibling;
359
+
360
+ // Remove all pre-rendered content between comments
345
361
  let current = startComment.nextSibling;
346
362
  while (current && current !== endComment) {
347
363
  const next = current.nextSibling;
348
- if (current.nodeType === Node.ELEMENT_NODE) {
364
+ if (current.nodeType !== Node.COMMENT_NODE) {
349
365
  current.remove();
350
366
  }
351
367
  current = next;
352
368
  }
353
369
 
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);
370
+ // Insert the template (single item)
371
+ const tempContainer = document.createElement('div');
372
+ tempContainer.innerHTML = restoration.template;
373
+
374
+ const fragment = document.createDocumentFragment();
375
+ while (tempContainer.firstChild) {
376
+ fragment.appendChild(tempContainer.firstChild);
360
377
  }
378
+ parent.insertBefore(fragment, endComment);
361
379
  }
362
380
 
363
- delete childTree.hyperspeedRestoration;
381
+ // Delete the iteration node from tree - runtime will re-create it
382
+ delete tree.children[key];
364
383
  continue;
365
384
  }
366
385
 
367
- // Handle conditional nodes (similar approach)
368
- if (childTree.type === 'conditional') {
369
- // TODO: Implement conditional restoration when needed
370
- continue;
371
- }
386
+ if (childTree.type === 'conditional' && childTree.compiled?.restoration) {
387
+ const restoration = childTree.compiled.restoration;
372
388
 
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
- );
389
+ // Find conditional comment markers in the current element
390
+ // But SKIP conditionals that are inside iteration blocks
391
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_COMMENT);
392
+ let startComment = null;
393
+ let endComment = null;
394
+ let insideIteration = false;
380
395
 
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
- }
396
+ while (walker.nextNode()) {
397
+ const comment = walker.currentNode;
398
+ const text = comment.textContent.trim();
399
+
400
+ // Track if we're inside an iteration block
401
+ if (text.startsWith('each ')) {
402
+ insideIteration = true;
403
+ continue;
404
+ } else if (text === '/each') {
405
+ insideIteration = false;
406
+ continue;
407
+ }
408
+
409
+ // Skip conditionals inside iterations - runtime will handle them
410
+ if (insideIteration) continue;
411
+
412
+ // Skip already processed conditionals
413
+ if (comment._vibeProcessed) continue;
414
+
415
+ if (text.startsWith('if') && !startComment) {
416
+ startComment = comment;
417
+ } else if (text === '/if' && startComment && !endComment) {
418
+ endComment = comment;
419
+ break;
420
+ }
421
+ }
422
+
423
+ if (startComment && endComment) {
424
+ // Mark as processed
425
+ startComment._vibeProcessed = true;
426
+ endComment._vibeProcessed = true;
427
+
428
+ // Remove ALL pre-rendered content between start and end comments
429
+ // This includes the <!-- else --> marker from compiled HTML
430
+ let current = startComment.nextSibling;
431
+ while (current && current !== endComment) {
432
+ const next = current.nextSibling;
433
+ current.remove(); // Remove ALL nodes, including comment nodes
434
+ current = next;
388
435
  }
436
+
437
+ // Insert the template content BEFORE endComment
438
+ const tempContainer = document.createElement('div');
439
+ tempContainer.innerHTML = restoration.template;
440
+
441
+ const fragment = document.createDocumentFragment();
442
+ while (tempContainer.firstChild) {
443
+ fragment.appendChild(tempContainer.firstChild);
444
+ }
445
+ endComment.parentNode.insertBefore(fragment, endComment);
389
446
  }
390
- delete childTree.hyperspeedRestoration;
447
+
448
+ // Delete restoration data AND remove the conditional node from tree
449
+ // Runtime will re-create it from the restored DOM
450
+ delete tree.children[key];
451
+ }
452
+ }
453
+ }
454
+
455
+ // Recursively restore children (skip conditional/iteration children as they were already handled)
456
+ if (tree.children) {
457
+ for (const key in tree.children) {
458
+ const childTree = tree.children[key];
459
+
460
+ // Skip conditionals and iterations (already processed/deleted above)
461
+ if (childTree.type === 'conditional' || childTree.type === 'iteration') {
391
462
  continue;
392
463
  }
393
464
 
394
- // Find corresponding child element
395
- // Keys like "strong_0" use childNodes indices (includes text nodes/comments)
396
- const match = key.match(/_(\d+)$/);
465
+ // Skip text nodes - already handled before conditional/iteration processing
466
+ if (key.startsWith('text_')) {
467
+ continue;
468
+ }
469
+
470
+ // Find corresponding child element by tag name and index
471
+ // Keys like "layout_1" mean the node at childNodes index 1 (includes text nodes)
472
+ // NOT the second layout element
473
+ // Tag names can contain digits (h1, h2, etc.) so use [a-z0-9-]+
474
+ const match = key.match(/^([a-z0-9-]+)_(\d+)$/);
397
475
  if (match) {
398
- const index = parseInt(match[1], 10);
399
- const childNode = element.childNodes[index]; // Use childNodes (all nodes)
476
+ const tagName = match[1].toUpperCase();
477
+ const nodeIndex = parseInt(match[2], 10);
400
478
 
401
- // Only walk if it's an element node
402
- if (childNode && childNode.nodeType === Node.ELEMENT_NODE) {
479
+ // Get the node at this childNodes index
480
+ const childNode = element.childNodes[nodeIndex];
481
+
482
+ if (childNode && childNode.nodeType === Node.ELEMENT_NODE && childNode.nodeName === tagName) {
403
483
  walkTree(childTree, childNode);
404
484
  }
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
485
  }
415
486
  }
416
487
  }
@@ -420,6 +491,7 @@ export const restoreMarkersFromManifest = (rootElement, subtree, fullManifest =
420
491
  };
421
492
 
422
493
  // Top-level await to detect before module exports
423
- hyperspeedManifest = await detectHyperspeed();
494
+ hyperspeedData = await detectHyperspeed();
424
495
 
425
- export { hyperspeedManifest };
496
+ export const hyperspeedManifest = hyperspeedData?.manifest || null;
497
+ 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
+ }