AgentSkillsCN

antigravity-qwik-tailwind

在Antigravity项目中,为QwikJS + Tailwind 4提供防错建议与最佳实践。当您使用Qwik组件、Tailwind CSS、模态对话框,或在遇到“p0 is not a function”错误、@apply/@layer相关问题时,可使用此功能。

SKILL.md
--- frontmatter
name: antigravity-qwik-tailwind
description: Error prevention and best practices for QwikJS + Tailwind 4 in the Antigravity project. Use when working with Qwik components, Tailwind CSS, modal dialogs, or when encountering "p0 is not a function" errors or @apply/@layer issues.

Antigravity QwikJS + Tailwind 4 Error Prevention

This skill prevents common recurring errors in the Antigravity project caused by QwikJS optimization and Tailwind 4 CSS layer usage.

Critical Error Patterns to Avoid

1. Tailwind @apply with @layer Classes

PROBLEM: Using classes defined in @layer directives inside @apply causes compilation errors.

RULE: NEVER use @apply with classes defined inside @layer blocks.

WRONG:

css
/* globals.css */
@layer components {
  .custom-button {
    @apply px-4 py-2 bg-blue-500;
  }
}

/* component.css */
.my-element {
  @apply custom-button; /* ❌ ERROR: custom-button is in a @layer */
}

CORRECT APPROACH 1 - Direct Tailwind utilities:

css
.my-element {
  @apply px-4 py-2 bg-blue-500;
}

CORRECT APPROACH 2 - CSS nesting without @apply:

css
@layer components {
  .custom-button {
    @apply px-4 py-2 bg-blue-500;
  }
}

/* Use the class directly in HTML, not via @apply */

CORRECT APPROACH 3 - Plain CSS:

css
.my-element {
  padding-left: 1rem;
  padding-right: 1rem;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  background-color: rgb(59 130 246);
}

2. QwikJS Modal/Dialog Pattern - "p0 is not a function"

PROBLEM: Qwik's optimizer requires specific patterns for signal reactivity. Common with delete buttons opening confirmation modals.

SYMPTOM: Runtime error "p0 is not a function" when clicking buttons that trigger modals.

ROOT CAUSE: Improper signal handling or missing $() wrapper in event handlers.

WRONG PATTERNS:

typescript
// ❌ Pattern 1: Direct boolean toggle without proper signal
export default component$(() => {
  const showModal = useSignal(false);
  
  return (
    <button onClick$={() => showModal.value = true}>Delete</button>
  );
});

// ❌ Pattern 2: Passing non-serializable functions
export default component$(() => {
  const handleDelete = () => {
    // logic here
  };
  
  return <Modal onConfirm={handleDelete} />; // ❌ Not wrapped in $()
});

// ❌ Pattern 3: Using inline functions in props
<CustomModal 
  isOpen={showModal.value}
  onClose={() => showModal.value = false} // ❌ Not wrapped
/>

CORRECT PATTERNS:

typescript
// ✅ Pattern 1: Proper QwikCity signal handling
export default component$(() => {
  const showModal = useSignal(false);
  
  const handleOpenModal = $(() => {
    showModal.value = true;
  });
  
  const handleCloseModal = $(() => {
    showModal.value = false;
  });
  
  return (
    <>
      <button onClick$={handleOpenModal}>Delete</button>
      <CustomModal 
        isOpen={showModal.value}
        onClose$={handleCloseModal}
      />
    </>
  );
});

// ✅ Pattern 2: Using QRL (Qwik Runtime Language)
export default component$(() => {
  const showModal = useSignal(false);
  
  return (
    <>
      <button onClick$={() => {
        showModal.value = true;
      }}>
        Delete
      </button>
      <CustomModal 
        isOpen={showModal.value}
        onClose$={() => {
          showModal.value = false;
        }}
      />
    </>
  );
});

// ✅ Pattern 3: Modal with proper event typing
interface ModalProps {
  isOpen: boolean;
  onClose$?: QRL<() => void>; // Note the $ suffix and QRL type
  onConfirm$?: QRL<() => void>;
}

export const CustomModal = component$<ModalProps>(({ 
  isOpen, 
  onClose$, 
  onConfirm$ 
}) => {
  if (!isOpen) return null;
  
  return (
    <div class="modal-backdrop">
      <div class="modal-content">
        <button onClick$={onClose$}>Cancel</button>
        <button onClick$={onConfirm$}>Confirm</button>
      </div>
    </div>
  );
});

COMPLETE DELETE BUTTON WITH MODAL EXAMPLE:

typescript
import { component$, useSignal, $, type QRL } from '@builder.io/qwik';

interface DeleteConfirmModalProps {
  isOpen: boolean;
  itemName: string;
  onClose$?: QRL<() => void>;
  onConfirm$?: QRL<() => Promise<void>>;
}

export const DeleteConfirmModal = component$<DeleteConfirmModalProps>(({
  isOpen,
  itemName,
  onClose$,
  onConfirm$
}) => {
  if (!isOpen) return null;
  
  return (
    <div class="modal-overlay">
      <div class="modal-container">
        <h3>Confirm Delete</h3>
        <p>Are you sure you want to delete "{itemName}"?</p>
        <div class="modal-actions">
          <button onClick$={onClose$} class="btn-cancel">
            Cancel
          </button>
          <button onClick$={onConfirm$} class="btn-delete">
            Delete
          </button>
        </div>
      </div>
    </div>
  );
});

// Usage in parent component
export default component$(() => {
  const showDeleteModal = useSignal(false);
  const itemToDelete = useSignal<string>('');
  
  const handleDeleteClick = $((name: string) => {
    itemToDelete.value = name;
    showDeleteModal.value = true;
  });
  
  const handleConfirmDelete = $(async () => {
    // Perform delete operation
    await deleteItem(itemToDelete.value);
    showDeleteModal.value = false;
  });
  
  const handleCancelDelete = $(() => {
    showDeleteModal.value = false;
  });
  
  return (
    <>
      <button onClick$={() => handleDeleteClick('Item 1')}>
        Delete Item
      </button>
      
      <DeleteConfirmModal
        isOpen={showDeleteModal.value}
        itemName={itemToDelete.value}
        onClose$={handleCancelDelete}
        onConfirm$={handleConfirmDelete}
      />
    </>
  );
});

Qwik Serialization Rules

To avoid "p0 is not a function" errors, follow these serialization rules:

  1. All event handlers must use $() wrapper or $ suffix

    • onClick$={handler} not onClick={handler}
    • Or wrap: const handler = $(() => { ... })
  2. Props that are functions must be QRL type

    • Type: onAction$?: QRL<() => void>
    • Pass with $ suffix in prop name
  3. Avoid closures over non-serializable values

    • Use useSignal() or useStore() for reactive state
    • Pass primitives or serializable objects only
  4. Use useVisibleTask$ for side effects, not useEffect

  5. Server functions use routeAction$ or routeLoader$

CSS Architecture Rules

  1. Never mix @layer and @apply

    • Define utility classes in @layer utilities
    • Define component classes in @layer components
    • Use these classes directly in HTML, not in other CSS via @apply
  2. Prefer semantic class naming

    • Create .card-container, .button-primary classes
    • Use @apply inside these with Tailwind utilities
    • Reference semantic classes in HTML
  3. File organization

    • Global layers: src/global.css
    • Component styles: component-name.css (co-located)
    • Never import layer-based CSS into component CSS for @apply usage

Pre-flight Checklist

Before committing code with modals or dialogs:

  • All event handlers use $() or have $ suffix
  • Modal props expecting functions use QRL<> type
  • No @apply used with @layer defined classes
  • Signals used for reactive boolean states
  • No direct DOM manipulation (use Qwik's reactivity)
  • Test the modal open/close cycle thoroughly

Testing Modal Components

Run this test pattern for all delete/confirm modals:

typescript
// E2E test pattern
test('delete modal workflow', async ({ page }) => {
  await page.goto('/your-page');
  
  // Click delete button
  await page.click('[data-testid="delete-button"]');
  
  // Modal should appear
  await expect(page.locator('[data-testid="delete-modal"]')).toBeVisible();
  
  // Cancel should close modal
  await page.click('[data-testid="modal-cancel"]');
  await expect(page.locator('[data-testid="delete-modal"]')).not.toBeVisible();
  
  // Reopen and confirm
  await page.click('[data-testid="delete-button"]');
  await page.click('[data-testid="modal-confirm"]');
  
  // Verify deletion (check for success message or item removal)
  await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});

Resources

  • references/qwik-signals-deep-dive.md - Advanced signal patterns and troubleshooting
  • references/tailwind4-migration.md - Tailwind 3 to 4 migration gotchas