diff --git a/solutions/cometchat-chat/.agents/skills/cometchat-astro-patterns/SKILL.md b/solutions/cometchat-chat/.agents/skills/cometchat-astro-patterns/SKILL.md
new file mode 100644
index 0000000000..499b0f3230
--- /dev/null
+++ b/solutions/cometchat-chat/.agents/skills/cometchat-astro-patterns/SKILL.md
@@ -0,0 +1,687 @@
+---
+name: cometchat-astro-patterns
+description: "Framework-specific patterns for integrating CometChat React UI Kit v6 into Astro projects using React islands. Covers client:only rendering, island communication, CSS handling, and common pitfalls."
+license: "MIT"
+compatibility: "Node.js >=18; React >=18; Astro >=3; @astrojs/react; @cometchat/chat-uikit-react ^6; @cometchat/chat-sdk-javascript ^4"
+allowed-tools: "executeBash, readFile, fileSearch, listDirectory"
+metadata:
+ author: "CometChat"
+ version: "3.0.0"
+ tags: "chat cometchat astro react islands client-only patterns"
+---
+
+## Purpose
+
+This skill teaches Claude how to integrate CometChat into an Astro project using React islands. Astro is a static-first framework -- most of the page is HTML rendered at build time. Interactive React components run as isolated "islands" in the browser. CometChat components are React islands that must use `client:only="react"` to bypass server rendering entirely.
+
+**Read these companion skills first:**
+- `cometchat-core` -- initialization, login, CSS, provider pattern, anti-patterns
+- `cometchat-components` -- component catalog and composition patterns
+- `cometchat-placement` -- WHERE to put chat (route, modal, drawer, embedded)
+
+---
+
+## 1. Project detection
+
+A project uses Astro when `package.json` has `astro` as a dependency. CometChat integration also requires the React integration:
+
+```bash
+# Check for Astro + React
+grep -E '"astro"|"@astrojs/react"' package.json
+```
+
+**If `@astrojs/react` is missing**, it must be installed first:
+
+```bash
+npx astro add react
+```
+
+This adds `@astrojs/react` to `package.json` and configures it in `astro.config.mjs`.
+
+Verify the React integration is configured:
+
+```bash
+# astro.config.mjs should have react() in the integrations array
+grep -A 5 "integrations" astro.config.mjs 2>/dev/null || grep -A 5 "integrations" astro.config.ts 2>/dev/null
+```
+
+---
+
+## 2. Critical: client:only="react"
+
+Every Astro component that renders CometChat MUST use `client:only="react"`. This is the single most important rule for Astro + CometChat.
+
+### Why client:only and not client:load or client:visible
+
+Astro's client directives control when and how interactive components are hydrated:
+
+| Directive | Server renders? | When hydrates? | Works with CometChat? |
+|---|---|---|---|
+| `client:load` | Yes | On page load | **NO** -- server render crashes |
+| `client:visible` | Yes | When visible in viewport | **NO** -- server render crashes |
+| `client:idle` | Yes | When browser is idle | **NO** -- server render crashes |
+| `client:only="react"` | No | On page load (client-only) | **YES** |
+
+`client:load`, `client:visible`, and `client:idle` all attempt to render the component on the server first, then hydrate in the browser. CometChat components access `window` and `document` at import time, so server rendering crashes with `ReferenceError: window is not defined`.
+
+`client:only="react"` skips server rendering entirely. The component only runs in the browser. This is the ONLY valid directive for CometChat.
+
+### Usage in .astro files
+
+```astro
+---
+import ChatView from "../components/ChatView";
+---
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## 3. CometChatProvider for Astro
+
+The provider pattern is the same React component as in other frameworks, but in Astro it runs entirely inside a React island. The provider is not mounted at the Astro layout level -- it is mounted inside the React island.
+
+### Full implementation
+
+```tsx
+// src/components/CometChatProvider.tsx
+import React, { useEffect, useState, createContext, useContext } from "react";
+import { CometChatUIKit, UIKitSettingsBuilder } from "@cometchat/chat-uikit-react";
+import "@cometchat/chat-uikit-react/css-variables.css";
+
+interface CometChatContextValue {
+ isReady: boolean;
+ error: string | null;
+}
+
+const CometChatContext = createContext({
+ isReady: false,
+ error: null,
+});
+
+export const useCometChat = () => useContext(CometChatContext);
+
+// Module-level state prevents both double-init AND double-login in React
+// StrictMode. Without the loginInFlight guard, a second mount calls
+// login() while the first is still pending and the SDK throws
+// "Please wait until the previous login request ends."
+let initialized = false;
+let loginInFlight: Promise | null = null;
+
+async function ensureLoggedIn(
+ uid: string,
+ authToken?: string,
+): Promise {
+ const existing = await CometChatUIKit.getLoggedinUser();
+ if (existing) return;
+ if (loginInFlight) {
+ await loginInFlight;
+ return;
+ }
+ loginInFlight = authToken
+ ? CometChatUIKit.loginWithAuthToken(authToken)
+ : CometChatUIKit.login(uid);
+ try {
+ await loginInFlight;
+ } finally {
+ loginInFlight = null;
+ }
+}
+
+interface CometChatProviderProps {
+ children: React.ReactNode;
+}
+
+export function CometChatProvider({ children }: CometChatProviderProps) {
+ const [isReady, setIsReady] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ async function setup() {
+ try {
+ if (!initialized) {
+ initialized = true;
+
+ const settings = new UIKitSettingsBuilder()
+ .setAppId(import.meta.env.PUBLIC_COMETCHAT_APP_ID)
+ .setRegion(import.meta.env.PUBLIC_COMETCHAT_REGION)
+ .setAuthKey(import.meta.env.PUBLIC_COMETCHAT_AUTH_KEY)
+ .subscribePresenceForAllUsers()
+ .build();
+
+ await CometChatUIKit.init(settings);
+ }
+
+ await ensureLoggedIn("cometchat-uid-1"); // DEVELOPMENT ONLY — see cometchat-production skill
+
+ setIsReady(true);
+ } catch (e) {
+ setError(String(e));
+ }
+ }
+
+ setup();
+ }, []);
+
+ if (error) {
+ return (
+
+ CometChat Error: {error}
+
+ );
+ }
+
+ if (!isReady) return null;
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+### Key difference: CSS import is INSIDE the component
+
+Notice that `@cometchat/chat-uikit-react/css-variables.css` is imported inside the React component file, not in an Astro layout or global stylesheet. This is because `client:only` islands are completely isolated from Astro's CSS pipeline. Stylesheets imported in `.astro` files or global CSS do NOT reach `client:only` islands.
+
+---
+
+## 4. Chat page with React island
+
+### Full page implementation
+
+```astro
+---
+// src/pages/messages.astro
+import Layout from "../layouts/Layout.astro";
+import ChatView from "../components/ChatView";
+---
+
+
+
+ );
+}
+```
+
+**Important:** The `CometChatProvider` wraps the content INSIDE the React island, not at the Astro level. Each island is an independent React tree. The provider initializes CometChat when this specific island mounts.
+
+---
+
+## 5. Drawer and modal patterns
+
+### Chat drawer as a React island
+
+```tsx
+// src/components/ChatDrawerIsland.tsx
+import { useState, useEffect } from "react";
+import { CometChatProvider } from "./CometChatProvider";
+import {
+ CometChatMessageHeader,
+ CometChatMessageList,
+ CometChatMessageComposer,
+} from "@cometchat/chat-uikit-react";
+import { CometChat } from "@cometchat/chat-sdk-javascript";
+
+interface ChatDrawerIslandProps {
+ targetUserId: string;
+}
+
+export default function ChatDrawerIsland({ targetUserId }: ChatDrawerIslandProps) {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+
+ {isOpen && (
+ <>
+
+
+ >
+ );
+}
+```
+
+Usage in an Astro page:
+
+```astro
+---
+import Layout from "../layouts/Layout.astro";
+import ChatDrawerIsland from "../components/ChatDrawerIsland";
+---
+
+
+
Product Details
+
Some product description...
+
+
+```
+
+**Note:** The trigger button is INSIDE the React island. Astro's static HTML cannot trigger React state changes directly. The button must be part of the same React tree.
+
+---
+
+## 6. Inter-island communication
+
+Astro's island architecture means different React islands are separate React trees. They cannot share React state, context, or refs. If you need communication between a navbar island and a chat island, use one of these approaches:
+
+### Option A: Custom DOM events
+
+```tsx
+// NavbarIsland.tsx -- fires a custom event
+function NavbarIsland() {
+ function openChat() {
+ window.dispatchEvent(new CustomEvent("open-chat", { detail: { userId: "uid-123" } }));
+ }
+
+ return ;
+}
+
+// ChatIsland.tsx -- listens for the event
+function ChatIsland() {
+ const [isOpen, setIsOpen] = useState(false);
+ const [targetUserId, setTargetUserId] = useState();
+
+ useEffect(() => {
+ function handleOpenChat(e: CustomEvent) {
+ setTargetUserId(e.detail.userId);
+ setIsOpen(true);
+ }
+
+ window.addEventListener("open-chat", handleOpenChat as EventListener);
+ return () => window.removeEventListener("open-chat", handleOpenChat as EventListener);
+ }, []);
+
+ // ... render chat drawer when isOpen
+}
+```
+
+### Option B: Nanostores (Astro's recommended approach)
+
+Install nanostores: `npm install nanostores @nanostores/react`
+
+```typescript
+// src/stores/chatStore.ts
+import { atom } from "nanostores";
+
+export const $chatOpen = atom(false);
+export const $chatTargetUserId = atom(undefined);
+```
+
+```tsx
+// NavbarIsland.tsx
+import { useStore } from "@nanostores/react";
+import { $chatOpen, $chatTargetUserId } from "../stores/chatStore";
+
+function NavbarIsland() {
+ function openChat() {
+ $chatTargetUserId.set("uid-123");
+ $chatOpen.set(true);
+ }
+
+ return ;
+}
+```
+
+```tsx
+// ChatIsland.tsx
+import { useStore } from "@nanostores/react";
+import { $chatOpen, $chatTargetUserId } from "../stores/chatStore";
+
+function ChatIsland() {
+ const isOpen = useStore($chatOpen);
+ const targetUserId = useStore($chatTargetUserId);
+
+ // ... render chat drawer when isOpen
+}
+```
+
+Nanostores work across framework boundaries -- if the project also has Svelte or Vue islands, they can all share the same store.
+
+### Option C: URL-based state
+
+Navigate to a chat page with query parameters:
+
+```astro
+
+Message this user
+```
+
+```tsx
+// ChatView.tsx -- reads user from URL
+function ChatView() {
+ const params = new URLSearchParams(window.location.search);
+ const targetUserId = params.get("user");
+
+ // ... resolve and render chat for this user
+}
+```
+
+---
+
+## 7. Environment variables
+
+### Astro env var conventions
+
+Astro uses Vite under the hood. Client-side variables must have the `PUBLIC_` prefix:
+
+```env
+PUBLIC_COMETCHAT_APP_ID=your_app_id
+PUBLIC_COMETCHAT_REGION=us
+PUBLIC_COMETCHAT_AUTH_KEY=your_auth_key
+```
+
+**Access in code:** `import.meta.env.PUBLIC_COMETCHAT_APP_ID`
+
+Variables without `PUBLIC_` prefix are server-only (available in Astro's frontmatter and API routes, but not in client islands).
+
+### .env file
+
+Create `.env` in the project root. Astro's `.env` is NOT gitignored by default -- add it:
+
+```bash
+echo ".env" >> .gitignore
+```
+
+### Server-only variables (for production auth)
+
+```env
+# Server-only (no PUBLIC_ prefix) -- for API endpoints
+COMETCHAT_AUTH_TOKEN=your_server_secret
+COMETCHAT_APP_ID=your_app_id
+COMETCHAT_REGION=us
+```
+
+Access in Astro API routes or server-side code:
+
+```typescript
+// src/pages/api/cometchat-token.ts
+import type { APIRoute } from "astro";
+
+export const POST: APIRoute = async ({ request }) => {
+ const { uid } = await request.json();
+ const appId = import.meta.env.COMETCHAT_APP_ID;
+ const region = import.meta.env.COMETCHAT_REGION;
+ const authToken = import.meta.env.COMETCHAT_AUTH_TOKEN;
+
+ const response = await fetch(
+ `https://${appId}.api-${region}.cometchat.io/v3/users/${uid}/auth_tokens`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ apiKey: authToken,
+ appId: appId,
+ },
+ body: JSON.stringify({}),
+ }
+ );
+
+ const data = await response.json();
+ return new Response(JSON.stringify({ token: data.data.authToken }), {
+ headers: { "Content-Type": "application/json" },
+ });
+};
+```
+
+**Note:** Astro API routes require on-demand rendering. In Astro 3: set `output: "server"` or `output: "hybrid"` in `astro.config.mjs`. In Astro 4+: the default is `output: "static"` with per-route opt-in — add `export const prerender = false;` at the top of the API route file. If the project is fully static, the auth endpoint must be hosted elsewhere.
+
+---
+
+## 8. CSS handling
+
+### The island CSS isolation problem
+
+Unlike other frameworks, Astro's `client:only` islands are completely isolated from the Astro CSS pipeline. CSS imported in `.astro` files, global stylesheets linked in ``, and `