{"id":"3edaed5b-96c1-487f-8299-297c1e4fc362","shortId":"CrwSxf","kind":"skill","title":"cometchat-astro-patterns","tagline":"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.","description":"## Purpose\n\nThis 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.\n\n**Read these companion skills first:**\n- `cometchat-core` -- initialization, login, CSS, provider pattern, anti-patterns\n- `cometchat-components` -- component catalog and composition patterns\n- `cometchat-placement` -- WHERE to put chat (route, modal, drawer, embedded)\n\n---\n\n## 1. Project detection\n\nA project uses Astro when `package.json` has `astro` as a dependency. CometChat integration also requires the React integration:\n\n```bash\n# Check for Astro + React\ngrep -E '\"astro\"|\"@astrojs/react\"' package.json\n```\n\n**If `@astrojs/react` is missing**, it must be installed first:\n\n```bash\nnpx astro add react\n```\n\nThis adds `@astrojs/react` to `package.json` and configures it in `astro.config.mjs`.\n\nVerify the React integration is configured:\n\n```bash\n# astro.config.mjs should have react() in the integrations array\ngrep -A 5 \"integrations\" astro.config.mjs 2>/dev/null || grep -A 5 \"integrations\" astro.config.ts 2>/dev/null\n```\n\n---\n\n## 2. Critical: client:only=\"react\"\n\nEvery Astro component that renders CometChat MUST use `client:only=\"react\"`. This is the single most important rule for Astro + CometChat.\n\n### Why client:only and not client:load or client:visible\n\nAstro's client directives control when and how interactive components are hydrated:\n\n| Directive | Server renders? | When hydrates? | Works with CometChat? |\n|---|---|---|---|\n| `client:load` | Yes | On page load | **NO** -- server render crashes |\n| `client:visible` | Yes | When visible in viewport | **NO** -- server render crashes |\n| `client:idle` | Yes | When browser is idle | **NO** -- server render crashes |\n| `client:only=\"react\"` | No | On page load (client-only) | **YES** |\n\n`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`.\n\n`client:only=\"react\"` skips server rendering entirely. The component only runs in the browser. This is the ONLY valid directive for CometChat.\n\n### Usage in .astro files\n\n```astro\n---\nimport ChatView from \"../components/ChatView\";\n---\n\n<!-- CORRECT -->\n<ChatView client:only=\"react\" />\n\n<!-- WRONG -- will crash during build -->\n<ChatView client:load />\n\n<!-- WRONG -- will crash during build -->\n<ChatView client:visible />\n\n<!-- WRONG -- no directive, component won't be interactive at all -->\n<ChatView />\n```\n\n---\n\n## 3. CometChatProvider for Astro\n\nThe 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.\n\n### Full implementation\n\n```tsx\n// src/components/CometChatProvider.tsx\nimport React, { useEffect, useState, createContext, useContext } from \"react\";\nimport { CometChatUIKit, UIKitSettingsBuilder } from \"@cometchat/chat-uikit-react\";\nimport \"@cometchat/chat-uikit-react/css-variables.css\";\n\ninterface CometChatContextValue {\n  isReady: boolean;\n  error: string | null;\n}\n\nconst CometChatContext = createContext<CometChatContextValue>({\n  isReady: false,\n  error: null,\n});\n\nexport const useCometChat = () => useContext(CometChatContext);\n\n// Module-level state prevents both double-init AND double-login in React\n// StrictMode. Without the loginInFlight guard, a second mount calls\n// login() while the first is still pending and the SDK throws\n// \"Please wait until the previous login request ends.\"\nlet initialized = false;\nlet loginInFlight: Promise<unknown> | null = null;\n\nasync function ensureLoggedIn(\n  uid: string,\n  authToken?: string,\n): Promise<void> {\n  const existing = await CometChatUIKit.getLoggedinUser();\n  if (existing) return;\n  if (loginInFlight) {\n    await loginInFlight;\n    return;\n  }\n  loginInFlight = authToken\n    ? CometChatUIKit.loginWithAuthToken(authToken)\n    : CometChatUIKit.login(uid);\n  try {\n    await loginInFlight;\n  } finally {\n    loginInFlight = null;\n  }\n}\n\ninterface CometChatProviderProps {\n  children: React.ReactNode;\n}\n\nexport function CometChatProvider({ children }: CometChatProviderProps) {\n  const [isReady, setIsReady] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    async function setup() {\n      try {\n        if (!initialized) {\n          initialized = true;\n\n          const settings = new UIKitSettingsBuilder()\n            .setAppId(import.meta.env.PUBLIC_COMETCHAT_APP_ID)\n            .setRegion(import.meta.env.PUBLIC_COMETCHAT_REGION)\n            .setAuthKey(import.meta.env.PUBLIC_COMETCHAT_AUTH_KEY)\n            .subscribePresenceForAllUsers()\n            .build();\n\n          await CometChatUIKit.init(settings);\n        }\n\n        await ensureLoggedIn(\"cometchat-uid-1\"); // DEVELOPMENT ONLY — see cometchat-production skill\n\n        setIsReady(true);\n      } catch (e) {\n        setError(String(e));\n      }\n    }\n\n    setup();\n  }, []);\n\n  if (error) {\n    return (\n      <div style={{ color: \"red\", padding: 16, fontFamily: \"monospace\" }}>\n        CometChat Error: {error}\n      </div>\n    );\n  }\n\n  if (!isReady) return null;\n\n  return (\n    <CometChatContext.Provider value={{ isReady, error }}>\n      {children}\n    </CometChatContext.Provider>\n  );\n}\n```\n\n### Key difference: CSS import is INSIDE the component\n\nNotice 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.\n\n---\n\n## 4. Chat page with React island\n\n### Full page implementation\n\n```astro\n---\n// src/pages/messages.astro\nimport Layout from \"../layouts/Layout.astro\";\nimport ChatView from \"../components/ChatView\";\n---\n\n<Layout title=\"Messages\">\n  <div class=\"chat-container\">\n    <ChatView client:only=\"react\" />\n  </div>\n</Layout>\n\n<style>\n  .chat-container {\n    height: calc(100vh - 64px); /* subtract header height */\n    width: 100%;\n  }\n</style>\n```\n\n### ChatView component\n\n```tsx\n// src/components/ChatView.tsx\nimport { useState } from \"react\";\nimport { CometChatProvider } from \"./CometChatProvider\";\nimport {\n  CometChatConversations,\n  CometChatMessageHeader,\n  CometChatMessageList,\n  CometChatMessageComposer,\n} from \"@cometchat/chat-uikit-react\";\nimport { CometChat } from \"@cometchat/chat-sdk-javascript\";\n\nexport default function ChatView() {\n  return (\n    <CometChatProvider>\n      <ChatContent />\n    </CometChatProvider>\n  );\n}\n\nfunction ChatContent() {\n  const [selectedUser, setSelectedUser] = useState<CometChat.User>();\n  const [selectedGroup, setSelectedGroup] = useState<CometChat.Group>();\n\n  function handleConversationClick(conversation: CometChat.Conversation) {\n    const entity = conversation.getConversationWith();\n    if (entity instanceof CometChat.User) {\n      setSelectedUser(entity);\n      setSelectedGroup(undefined);\n    } else if (entity instanceof CometChat.Group) {\n      setSelectedUser(undefined);\n      setSelectedGroup(entity);\n    }\n  }\n\n  return (\n    <div style={{ display: \"flex\", height: \"100%\" }}>\n      <div style={{ width: \"360px\", borderRight: \"1px solid #eee\" }}>\n        <CometChatConversations onItemClick={handleConversationClick} />\n      </div>\n      <div style={{ flex: 1, display: \"flex\", flexDirection: \"column\" }}>\n        {(selectedUser || selectedGroup) ? (\n          <>\n            {selectedUser && <CometChatMessageHeader user={selectedUser} />}\n            {selectedGroup && <CometChatMessageHeader group={selectedGroup} />}\n            {selectedUser && <CometChatMessageList user={selectedUser} hideReplyInThreadOption={true} />}\n            {selectedGroup && <CometChatMessageList group={selectedGroup} hideReplyInThreadOption={true} />}\n            {selectedUser && <CometChatMessageComposer user={selectedUser} />}\n            {selectedGroup && <CometChatMessageComposer group={selectedGroup} />}\n          </>\n        ) : (\n          <div style={{ flex: 1, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", color: \"#999\" }}>\n            Select a conversation to start chatting\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n```\n\n**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.\n\n---\n\n## 5. Drawer and modal patterns\n\n### Chat drawer as a React island\n\n```tsx\n// src/components/ChatDrawerIsland.tsx\nimport { useState, useEffect } from \"react\";\nimport { CometChatProvider } from \"./CometChatProvider\";\nimport {\n  CometChatMessageHeader,\n  CometChatMessageList,\n  CometChatMessageComposer,\n} from \"@cometchat/chat-uikit-react\";\nimport { CometChat } from \"@cometchat/chat-sdk-javascript\";\n\ninterface ChatDrawerIslandProps {\n  targetUserId: string;\n}\n\nexport default function ChatDrawerIsland({ targetUserId }: ChatDrawerIslandProps) {\n  const [isOpen, setIsOpen] = useState(false);\n\n  return (\n    <CometChatProvider>\n      <button onClick={() => setIsOpen(true)}>Message</button>\n\n      {isOpen && (\n        <>\n          <div\n            onClick={() => setIsOpen(false)}\n            style={{ position: \"fixed\", inset: 0, zIndex: 999, backgroundColor: \"rgba(0,0,0,0.3)\" }}\n          />\n          <div style={{\n            position: \"fixed\", top: 0, right: 0, bottom: 0, width: \"400px\", zIndex: 1000,\n            backgroundColor: \"#fff\", boxShadow: \"-4px 0 20px rgba(0,0,0,0.15)\",\n            display: \"flex\", flexDirection: \"column\",\n          }}>\n            <div style={{ display: \"flex\", justifyContent: \"space-between\", padding: \"12px\", borderBottom: \"1px solid #eee\" }}>\n              <span style={{ fontWeight: 600 }}>Chat</span>\n              <button onClick={() => setIsOpen(false)} style={{ background: \"none\", border: \"none\", cursor: \"pointer\" }} aria-label=\"Close\">&times;</button>\n            </div>\n            <DrawerContent targetUserId={targetUserId} />\n          </div>\n        </>\n      )}\n    </CometChatProvider>\n  );\n}\n\nfunction DrawerContent({ targetUserId }: { targetUserId: string }) {\n  const [user, setUser] = useState<CometChat.User>();\n\n  useEffect(() => {\n    CometChat.getUser(targetUserId).then(setUser);\n  }, [targetUserId]);\n\n  if (!user) return <div style={{ padding: 16 }}>Loading...</div>;\n\n  return (\n    <>\n      <CometChatMessageHeader user={user} />\n      <div style={{ flex: 1, overflow: \"hidden\" }}>\n        <CometChatMessageList user={user} hideReplyInThreadOption={true} />\n      </div>\n      <CometChatMessageComposer user={user} />\n    </>\n  );\n}\n```\n\nUsage in an Astro page:\n\n```astro\n---\nimport Layout from \"../layouts/Layout.astro\";\nimport ChatDrawerIsland from \"../components/ChatDrawerIsland\";\n---\n\n<Layout title=\"Product\">\n  <h1>Product Details</h1>\n  <p>Some product description...</p>\n  <ChatDrawerIsland client:only=\"react\" targetUserId=\"seller-uid-123\" />\n</Layout>\n```\n\n**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.\n\n---\n\n## 6. Inter-island communication\n\nAstro'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:\n\n### Option A: Custom DOM events\n\n```tsx\n// NavbarIsland.tsx -- fires a custom event\nfunction NavbarIsland() {\n  function openChat() {\n    window.dispatchEvent(new CustomEvent(\"open-chat\", { detail: { userId: \"uid-123\" } }));\n  }\n\n  return <button onClick={openChat}>Messages</button>;\n}\n\n// ChatIsland.tsx -- listens for the event\nfunction ChatIsland() {\n  const [isOpen, setIsOpen] = useState(false);\n  const [targetUserId, setTargetUserId] = useState<string>();\n\n  useEffect(() => {\n    function handleOpenChat(e: CustomEvent) {\n      setTargetUserId(e.detail.userId);\n      setIsOpen(true);\n    }\n\n    window.addEventListener(\"open-chat\", handleOpenChat as EventListener);\n    return () => window.removeEventListener(\"open-chat\", handleOpenChat as EventListener);\n  }, []);\n\n  // ... render chat drawer when isOpen\n}\n```\n\n### Option B: Nanostores (Astro's recommended approach)\n\nInstall nanostores: `npm install nanostores @nanostores/react`\n\n```typescript\n// src/stores/chatStore.ts\nimport { atom } from \"nanostores\";\n\nexport const $chatOpen = atom(false);\nexport const $chatTargetUserId = atom<string | undefined>(undefined);\n```\n\n```tsx\n// NavbarIsland.tsx\nimport { useStore } from \"@nanostores/react\";\nimport { $chatOpen, $chatTargetUserId } from \"../stores/chatStore\";\n\nfunction NavbarIsland() {\n  function openChat() {\n    $chatTargetUserId.set(\"uid-123\");\n    $chatOpen.set(true);\n  }\n\n  return <button onClick={openChat}>Messages</button>;\n}\n```\n\n```tsx\n// ChatIsland.tsx\nimport { useStore } from \"@nanostores/react\";\nimport { $chatOpen, $chatTargetUserId } from \"../stores/chatStore\";\n\nfunction ChatIsland() {\n  const isOpen = useStore($chatOpen);\n  const targetUserId = useStore($chatTargetUserId);\n\n  // ... render chat drawer when isOpen\n}\n```\n\nNanostores work across framework boundaries -- if the project also has Svelte or Vue islands, they can all share the same store.\n\n### Option C: URL-based state\n\nNavigate to a chat page with query parameters:\n\n```astro\n<!-- In the navbar (static HTML or any island) -->\n<a href=\"/messages?user=uid-123\">Message this user</a>\n```\n\n```tsx\n// ChatView.tsx -- reads user from URL\nfunction ChatView() {\n  const params = new URLSearchParams(window.location.search);\n  const targetUserId = params.get(\"user\");\n\n  // ... resolve and render chat for this user\n}\n```\n\n---\n\n## 7. Environment variables\n\n### Astro env var conventions\n\nAstro uses Vite under the hood. Client-side variables must have the `PUBLIC_` prefix:\n\n```env\nPUBLIC_COMETCHAT_APP_ID=your_app_id\nPUBLIC_COMETCHAT_REGION=us\nPUBLIC_COMETCHAT_AUTH_KEY=your_auth_key\n```\n\n**Access in code:** `import.meta.env.PUBLIC_COMETCHAT_APP_ID`\n\nVariables without `PUBLIC_` prefix are server-only (available in Astro's frontmatter and API routes, but not in client islands).\n\n### .env file\n\nCreate `.env` in the project root. Astro's `.env` is NOT gitignored by default -- add it:\n\n```bash\necho \".env\" >> .gitignore\n```\n\n### Server-only variables (for production auth)\n\n```env\n# Server-only (no PUBLIC_ prefix) -- for API endpoints\nCOMETCHAT_AUTH_TOKEN=your_server_secret\nCOMETCHAT_APP_ID=your_app_id\nCOMETCHAT_REGION=us\n```\n\nAccess in Astro API routes or server-side code:\n\n```typescript\n// src/pages/api/cometchat-token.ts\nimport type { APIRoute } from \"astro\";\n\nexport const POST: APIRoute = async ({ request }) => {\n  const { uid } = await request.json();\n  const appId = import.meta.env.COMETCHAT_APP_ID;\n  const region = import.meta.env.COMETCHAT_REGION;\n  const authToken = import.meta.env.COMETCHAT_AUTH_TOKEN;\n\n  const response = await fetch(\n    `https://${appId}.api-${region}.cometchat.io/v3/users/${uid}/auth_tokens`,\n    {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        apiKey: authToken,\n        appId: appId,\n      },\n      body: JSON.stringify({}),\n    }\n  );\n\n  const data = await response.json();\n  return new Response(JSON.stringify({ token: data.data.authToken }), {\n    headers: { \"Content-Type\": \"application/json\" },\n  });\n};\n```\n\n**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.\n\n---\n\n## 8. CSS handling\n\n### The island CSS isolation problem\n\nUnlike other frameworks, Astro's `client:only` islands are completely isolated from the Astro CSS pipeline. CSS imported in `.astro` files, global stylesheets linked in `<head>`, and `<style>` tags in Astro layouts do NOT reach `client:only` React components.\n\n### Solution: import CSS inside the React component\n\n```tsx\n// src/components/ChatView.tsx\nimport \"@cometchat/chat-uikit-react/css-variables.css\"; // MUST be here, not in .astro\nimport { CometChatConversations } from \"@cometchat/chat-uikit-react\";\n\nexport default function ChatView() {\n  return <CometChatConversations />;\n}\n```\n\n### Where NOT to import CSS\n\n```astro\n---\n// src/layouts/Layout.astro\n// WRONG -- this CSS won't reach client:only islands\n---\n<html>\n  <head>\n    <link rel=\"stylesheet\" href=\"@cometchat/chat-uikit-react/css-variables.css\" />\n  </head>\n  <body><slot /></body>\n</html>\n```\n\n```css\n/* src/styles/global.css */\n/* WRONG -- @import here won't reach client:only islands */\n@import \"@cometchat/chat-uikit-react/css-variables.css\";\n```\n\n### Theming overrides\n\nTo override CometChat CSS variables in Astro, do it inside the React component:\n\n```tsx\n// src/components/ChatView.tsx\nimport \"@cometchat/chat-uikit-react/css-variables.css\";\nimport \"./cometchat-overrides.css\"; // your overrides, imported AFTER\n\nexport default function ChatView() {\n  // ...\n}\n```\n\n```css\n/* src/components/cometchat-overrides.css */\n:root {\n  --cometchat-primary-color: #6851d6;\n  --cometchat-font-family: \"Inter\", sans-serif;\n}\n```\n\n---\n\n## 9. Common pitfalls\n\n### client:load vs client:only\n\n**Symptom:** `ReferenceError: window is not defined` during `astro build` or `astro dev`.\n\n**Cause:** Using `client:load` (or `client:visible`, `client:idle`) instead of `client:only=\"react\"`. These directives attempt server-side rendering.\n\n**Fix:** Replace with `client:only=\"react\"`. Always. For every CometChat component.\n\n### CSS not appearing\n\n**Symptom:** CometChat components render with no styling -- raw unstyled HTML, missing colors, broken layout.\n\n**Cause:** CSS imported in an Astro layout or global stylesheet does not reach `client:only` islands.\n\n**Fix:** Import `@cometchat/chat-uikit-react/css-variables.css` inside the React component file (section 8).\n\n### View Transitions\n\nAstro's View Transitions API enables smooth page transitions without full reloads. CometChat's WebSocket connection persists across view transitions (the connection is on `window`, which survives transitions). However, the React island REMOUNTS on each navigation because Astro replaces the DOM.\n\nTo keep chat state across page navigations, add `transition:persist` to the island element:\n\n```astro\n<ChatView client:only=\"react\" transition:persist />\n```\n\nWith `transition:persist`, Astro keeps the same DOM element across navigations, so the React tree stays mounted and chat state is preserved. Without it, navigating away and back remounts the island, requiring re-initialization (the `initialized` flag prevents double-init, but the UI state resets).\n\n### Content Collections\n\nAstro's Content Collections are for static content (Markdown, MDX, JSON). Chat data is dynamic and comes from CometChat's SDK. Do not try to store or query chat data via Content Collections.\n\n### Multiple chat islands on one page\n\nIf a page has multiple `client:only=\"react\"` islands that use CometChat, each island is a separate React tree. The module-level `initialized` flag ensures CometChat only initializes once even with multiple islands (the flag is shared across imports of the same module).\n\nHowever, each island needs its own `CometChatProvider` wrapping its content. The provider's `isReady` state is local to each React tree.\n\n### Passing data from Astro to React islands\n\nProps passed to `client:only` components must be serializable (strings, numbers, booleans, plain objects, arrays). You cannot pass React components, functions, or class instances from Astro frontmatter:\n\n```astro\n---\n// CORRECT -- serializable props\nconst userId = \"cometchat-uid-1\";\n---\n<ChatDrawerIsland client:only=\"react\" targetUserId={userId} />\n\n---\n// WRONG -- function props are not serializable\nconst handleClose = () => console.log(\"closed\");\n---\n<ChatDrawerIsland client:only=\"react\" onClose={handleClose} />\n```\n\n---\n\n## 10. Complete integration checklist\n\n1. Ensure `@astrojs/react` is installed: `npx astro add react`\n2. Install packages: `npm install @cometchat/chat-uikit-react @cometchat/chat-sdk-javascript`\n3. Create `.env` with `PUBLIC_COMETCHAT_APP_ID`, `PUBLIC_COMETCHAT_REGION`, `PUBLIC_COMETCHAT_AUTH_KEY`\n4. Add `.env` to `.gitignore`\n5. Create `src/components/CometChatProvider.tsx` with CSS import inside it (section 3)\n6. Create `src/components/ChatView.tsx` wrapping content in `CometChatProvider` (section 4)\n7. Create `src/pages/messages.astro` with `<ChatView client:only=\"react\" />` (section 4)\n8. Add a \"Messages\" link to the Astro layout's nav\n9. Verify: `npm run build` should succeed without `window is not defined` errors\n\n**The three things to remember for Astro:**\n1. Always `client:only=\"react\"` -- never `client:load`\n2. CSS imports go INSIDE the React component -- never in `.astro` files\n3. Each island wraps its own `CometChatProvider` -- there is no global provider at the Astro level","tags":["cometchat","astro","patterns","skills","agent-skills","ai-agent","chat","claude-code","cursor","messaging","nextjs","react"],"capabilities":["skill","source-cometchat","skill-cometchat-astro-patterns","topic-agent-skills","topic-ai-agent","topic-chat","topic-claude-code","topic-cometchat","topic-cursor","topic-messaging","topic-nextjs","topic-react","topic-react-native","topic-ui-kit"],"categories":["cometchat-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cometchat/cometchat-skills/cometchat-astro-patterns","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cometchat/cometchat-skills","source_repo":"https://github.com/cometchat/cometchat-skills","install_from":"skills.sh"}},"qualityScore":"0.463","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 27 github stars · SKILL.md body (21,569 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:04:48.989Z","embedding":null,"createdAt":"2026-05-07T13:05:08.262Z","updatedAt":"2026-05-18T19:04:48.989Z","lastSeenAt":"2026-05-18T19:04:48.989Z","tsv":"'-123':1177,1276 '-4':972 '/auth_tokens':1548 '/cometchatprovider':728,905 '/components/chatdrawerisland':1076 '/components/chatview':381,716 '/dev/null':203,210 '/layouts/layout.astro':712,1072 '/stores/chatstore':1269,1294 '/v3/users/$':1546 '0':946,951,952,953,960,962,964,974,977,978,979 '0.15':980 '0.3':954 '1':127,604,800,838,1052 '100':785 '1000':968 '12px':994 '16':628,1043 '1px':791,996 '2':202,209,211 '20px':975 '3':382,1588 '360px':789 '4':698,1599 '400px':966 '5':199,206,884 '6':1111 '600':1002 '7':1373 '8':1638 '999':846,948 'access':334,1414,1496 'across':1312 'add':170,173,1458,1612 'alignitem':841 'also':143,1318 'anti':106 'anti-pattern':105 'api':1435,1479,1499,1542,1579,1622 'apikey':1556 'apirout':1510,1516 'app':583,1398,1401,1419,1488,1491,1526 'appid':1524,1541,1558,1559 'application/json':1555,1576 'approach':1152,1234 'architectur':1119 'aria':1016 'aria-label':1015 'array':196 'astro':3,17,44,49,133,137,151,155,169,217,235,247,375,377,385,400,415,665,680,687,707,866,1066,1068,1091,1116,1231,1345,1376,1380,1431,1450,1498,1512,1578,1587,1598,1649,1659,1665 'astro.config.mjs':181,189,201,1596 'astro.config.ts':208 'astrojs/react':156,159,174 'async':514,568,1517 'atom':1244,1250,1255 'attempt':318 'auth':592,1409,1412,1470,1482,1535,1632 'authtoken':519,535,537,1533,1557 'avail':1429 'await':524,531,541,596,599,1521,1539,1564 'b':1229 'background':1009 'backgroundcolor':949,969 'base':1335 'bash':148,167,188,1460 'bodi':1560 'boolean':447 'border':1011 'borderbottom':995 'borderright':790 'bottom':963 'boundari':1314 'boxshadow':971 'browser':75,292,331,364 'build':64,595 'button':932,1004,1085,1102,1179,1280 'bypass':88 'c':1332 'call':486 'cannot':1095,1129 'catalog':112 'catch':614 'center':842,844 'chang':1099 'chat':122,699,852,889,1003,1146,1173,1211,1219,1224,1306,1340,1369 'chatcont':746 'chatdrawerisland':923,1074 'chatdrawerislandprop':917,925 'chatisland':1189,1296 'chatisland.tsx':1183,1285 'chatopen':1249,1266,1291,1300 'chatopen.set':1277 'chattargetuserid':1254,1267,1292,1304 'chattargetuserid.set':1274 'chatview':379,714,717,743,1356 'chatview.tsx':1350 'check':149 'children':548,553,643 'claud':37 'client':23,84,213,224,238,242,245,249,267,277,288,299,307,310,312,315,351,673,695,1387,1440,1651 'client-on':306 'client-sid':1386 'close':1018 'code':1416,1505 'color':625,845 'column':804,984 'cometchat':2,11,41,76,98,109,117,141,221,236,266,332,372,582,587,591,602,609,631,737,878,913,1397,1404,1408,1418,1481,1487,1493 'cometchat-astro-pattern':1 'cometchat-compon':108 'cometchat-cor':97 'cometchat-plac':116 'cometchat-product':608 'cometchat-uid':601 'cometchat.conversation':758 'cometchat.getuser':1032 'cometchat.group':774 'cometchat.io':1545 'cometchat.io/v3/users/$':1544 'cometchat.user':765 'cometchat/chat-sdk-javascript':739,915 'cometchat/chat-uikit-react':441,735,911 'cometchat/chat-uikit-react/css-variables.css':443,654 'cometchatcontext':452,462 'cometchatcontext.provider':639 'cometchatcontextvalu':445 'cometchatconvers':730,794 'cometchatmessagecompos':733,828,832,909,1060 'cometchatmessagehead':731,808,812,907,1046 'cometchatmessagelist':732,816,822,908,1055 'cometchatprovid':383,552,726,855,903 'cometchatproviderprop':547,554 'cometchatuikit':438 'cometchatuikit.getloggedinuser':525 'cometchatuikit.init':597 'cometchatuikit.login':538 'cometchatuikit.loginwithauthtoken':536 'common':31 'communic':27,1115,1139 'companion':94 'complet':677,1655 'compon':68,77,110,111,218,256,322,333,359,393,651,660,718 'composit':114 'configur':178,187 'const':451,459,522,555,560,576,747,751,759,926,1027,1190,1195,1248,1253,1297,1301,1357,1362,1514,1519,1523,1528,1532,1537,1562,1614 'content':858,1553,1574 'content-typ':1552,1573 'context':1133 'control':251 'convent':1379 'convers':757,849 'conversation.getconversationwith':761 'core':99 'cover':22 'crash':276,287,298,344 'creat':1444 'createcontext':433,453 'critic':212 'css':28,102,646,682,691,1639,1643,1660,1662 'cursor':1013 'custom':1155,1162 'customev':1170,1203 'data':1563 'data.data.authtoken':1571 'default':741,921,1457,1601 'defin':350 'demand':1584 'depend':140 'descript':1081 'detail':1078,1174 'detect':129 'develop':605 'differ':645,1121 'direct':250,259,370,1100 'display':782,801,839,981,987 'div':623,780,786,797,835,938,955,985,1040,1049 'document':337 'dom':1156 'doubl':470,474 'double-init':469 'double-login':473 'drawer':125,885,890,1225,1307 'drawercont':1019,1023 'e':154,615,618,1202 'e.detail.userid':1205 'echo':1461 'eee':793,998 'els':770 'elsewher':1637 'embed':126 'end':505 'endpoint':1480,1633 'ensureloggedin':516,600 'entir':91,357,403 'entiti':760,763,767,772,778 'env':1377,1395,1442,1445,1452,1462,1471 'environ':1374 'error':448,456,561,621,632,633,642 'event':1157,1163,1187 'eventlisten':1214,1222 'everi':216 'exist':523,527 'export':458,550,740,920,1247,1252,1513,1613 'fals':455,508,559,930,941,1007,1194,1251,1616 'fetch':1540 'fff':970 'file':376,661,688,1443,1624,1666 'final':543 'fire':1160 'first':54,96,166,326,490 'fix':944,958 'flex':783,799,802,837,840,982,988,1051 'flexdirect':803,983 'fontfamili':629 'fontweight':1001 'framework':6,55,397,1313,1648 'framework-specif':5 'frontmatt':1433 'full':425,704 'fulli':1629 'function':515,551,569,742,745,755,922,1022,1164,1166,1188,1200,1270,1272,1295,1355 'gitignor':1455,1463 'global':668,690,1667 'grep':153,197,204 'group':813,823,833 'guard':482 'handl':29,1640 'handleconversationclick':756,796 'handleopenchat':1201,1212,1220 'header':1551,1572 'height':784 'hidden':1054 'hidereplyinthreadopt':819,825,1058 'hood':1385 'host':1636 'html':61,1094 'hybrid':1594 'hydrat':258,263,328 'id':584,1399,1402,1420,1489,1492,1527 'idl':289,294,316 'implement':426,706 'import':232,339,378,429,437,442,647,656,685,709,713,721,725,729,736,853,897,902,906,912,1069,1073,1243,1261,1265,1286,1290,1508,1663 'import.meta.env.cometchat':1525,1530,1534 'import.meta.env.public':581,586,590,1417 'independ':872 'init':471 'initi':100,507,573,574,877 'inset':945 'insid':404,421,649,657,859,1087 'instal':165,1235,1238 'instanceof':764,773 'integr':10,40,142,147,185,195,200,207 'inter':1113 'inter-island':1112 'interact':66,255 'interfac':444,546,916 'island':21,26,48,72,80,407,424,675,697,703,862,869,882,894,1090,1114,1118,1123,1143,1147,1323,1441,1642,1653 'isol':71,678,1644,1656 'isopen':927,937,1191,1227,1298,1309 'isreadi':446,454,556,635,641 'json.stringify':1561,1569 'justifycont':843,989 'key':593,644,1410,1413 'kit':14 'label':1017 'layout':416,666,710,1070 'let':506,509 'level':417,465,867 'link':1669 'listen':1184 'load':243,268,272,305,311,1044 'login':101,475,487,503 'logininflight':481,510,530,532,534,542,544 'mean':1120 'messag':936,1182,1283,1346 'method':1549 'miss':161 'modal':124,887 'modul':464 'module-level':463 'monospac':630 'mount':412,420,485,883 'must':82,163,222,1103,1390,1634 'nanostor':1230,1236,1239,1246,1310 'nanostores/react':1240,1264,1289 'navbar':1142 'navbarisland':1165,1271 'navbarisland.tsx':1159,1260 'navig':1337 'need':1138 'new':578,1169,1359,1567 'none':1010,1012 'note':1082,1577 'notic':652 'npm':1237 'npx':168 'null':450,457,512,513,545,565,566,637 'on-demand':1582 'onclick':933,939,1005,1180,1281 'one':1149 'onitemclick':795 'open':1172,1210,1218 'open-chat':1171,1209,1217 'openchat':1167,1181,1273,1282 'opt':1610 'opt-in':1609 'option':1153,1228,1331 'output':1590,1593,1603 'overflow':1053 'package.json':135,157,176 'pad':627,993,1042 'page':59,271,304,700,705,1067,1341 'param':1358 'paramet':1344 'params.get':1364 'part':1105 'pattern':4,8,104,107,115,388,888 'pend':493 'per':1607 'per-rout':1606 'pipelin':683,1661 'pitfal':32 'placement':118 'pleas':498 'pointer':1014 'posit':943,957 'post':1515,1550 'prefix':1394,1424,1477 'prerend':1615 'prevent':467 'previous':502 'problem':1645 'product':610,1077,1080,1469 'project':18,45,128,131,1317,1448,1627 'promis':511,521 'provid':103,387,409,876 'public':1393,1396,1403,1407,1423,1476 'purpos':33 'put':121 'px':973 'queri':1343 'reach':694 'react':12,20,47,67,79,86,146,152,171,184,192,215,226,301,353,392,406,423,430,436,477,659,702,724,861,873,893,901,1089,1097,1109,1122,1126,1131 'react.reactnode':549 'read':92,1351 'recommend':1233 'red':626 'ref':1135 'referenceerror':346 'region':588,1405,1494,1529,1531,1543 'render':25,62,90,220,261,275,286,297,320,343,356,1223,1305,1368,1585 'request':504,1518 'request.json':1522 'requir':144,1581 'resolv':1366 'respons':1538,1568 'response.json':1565 'return':528,533,622,636,638,744,779,931,1039,1045,1178,1215,1279,1566 'rgba':950,976 'right':961 'root':1449 'rout':123,1436,1500,1580,1608,1623 'rule':233 'run':69,361,402 'sdk':496 'second':484 'secret':1486 'see':607 'select':847 'selectedgroup':752,806,811,814,821,824,831,834 'selectedus':748,805,807,810,815,818,827,830 'separ':1125 'server':89,260,274,285,296,325,342,355,1427,1465,1473,1485,1503,1591 'server-on':1426,1464,1472 'server-sid':1502 'set':577,598,1589 'setappid':580 'setauthkey':589 'seterror':562,616 'setisopen':928,934,940,1006,1192,1206 'setisreadi':557,612 'setregion':585 'setselectedgroup':753,768,777 'setselectedus':749,766,775 'settargetuserid':1197,1204 'setup':570,619 'setus':1029,1035 'share':1130,1327 'side':1388,1504 'singl':230 'skill':35,95,611 'skill-cometchat-astro-patterns' 'skip':354 'solid':792,997 'source-cometchat' 'space':991 'space-between':990 'span':999 'specif':7,881 'src/components/chatdrawerisland.tsx':896 'src/components/chatview.tsx':720 'src/components/cometchatprovider.tsx':428 'src/pages/api/cometchat-token.ts':1507 'src/pages/messages.astro':708 'src/stores/chatstore.ts':1242 'start':851 'state':466,1098,1132,1336 'static':53,1093,1604,1630 'static-first':52 'still':492 'store':1330 'strictmod':478 'string':449,518,520,564,617,919,1026,1256 'style':624,781,787,798,836,942,956,986,1000,1008,1041,1050 'stylesheet':669,684,1668 'subscribepresenceforallus':594 'svelt':1320 'targetuserid':918,924,1020,1021,1024,1025,1033,1036,1196,1302,1363 'teach':36 'throw':497 'time':65,340 'token':1483,1536,1570 'top':959,1619 'topic-agent-skills' 'topic-ai-agent' 'topic-chat' 'topic-claude-code' 'topic-cometchat' 'topic-cursor' 'topic-messaging' 'topic-nextjs' 'topic-react' 'topic-react-native' 'topic-ui-kit' 'tree':874,1110,1127 'tri':540,571 'trigger':1084,1096 'true':575,613,820,826,935,1059,1207,1278 'tsx':427,719,895,1158,1259,1284,1349 'type':1509,1554,1575 'typescript':1241,1506 'ui':13 'uid':517,539,603,1176,1275,1520,1547 'uikitsettingsbuild':439,579 'undefin':769,776,1257,1258 'unlik':1646 'url':1334,1354 'url-bas':1333 'urlsearchparam':1360 'us':1406,1495 'usag':373,1063 'use':19,46,83,132,223,1148,1381 'usecometchat':460 'usecontext':434,461 'useeffect':431,567,899,1031,1199 'user':809,817,829,1028,1038,1047,1048,1056,1057,1061,1062,1348,1352,1365,1372 'userid':1175 'usest':432,558,563,722,750,754,898,929,1030,1193,1198 'usestor':1262,1287,1299,1303 'v6':15 'valid':369 'valu':640 'var':1378 'variabl':1375,1389,1421,1467 'verifi':182 'viewport':283 'visibl':246,278,281,313 'vite':1382 'vue':1322 'wait':499 'width':788,965 'window':335,347 'window.addeventlistener':1208 'window.dispatchevent':1168 'window.location.search':1361 'window.removeeventlistener':1216 'without':479,1422 'work':264,1311 'wrap':856 'yes':269,279,290,309 'zindex':947,967","prices":[{"id":"71d7b792-0093-4959-87ab-3dfe459bcdd8","listingId":"3edaed5b-96c1-487f-8299-297c1e4fc362","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cometchat","category":"cometchat-skills","install_from":"skills.sh"},"createdAt":"2026-05-07T13:05:08.262Z"}],"sources":[{"listingId":"3edaed5b-96c1-487f-8299-297c1e4fc362","source":"github","sourceId":"cometchat/cometchat-skills/cometchat-astro-patterns","sourceUrl":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-astro-patterns","isPrimary":false,"firstSeenAt":"2026-05-07T13:05:08.262Z","lastSeenAt":"2026-05-18T19:04:48.989Z"}],"details":{"listingId":"3edaed5b-96c1-487f-8299-297c1e4fc362","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cometchat","slug":"cometchat-astro-patterns","github":{"repo":"cometchat/cometchat-skills","stars":27,"topics":["agent-skills","ai-agent","chat","claude-code","cometchat","cursor","messaging","nextjs","react","react-native","ui-kit"],"license":null,"html_url":"https://github.com/cometchat/cometchat-skills","pushed_at":"2026-05-18T05:04:24Z","description":"Add CometChat chat to any React, Next.js, React Native, Angular, Android, iOS, or Flutter project through your AI coding agent. Works with Claude Code, Cursor, Codex, VS Code Copilot, Windsurf, Cline, Kiro, and 50+ more agents.","skill_md_sha":"dd1b56ac63abbb32fd0c8b8d67f75601fe96f07f","skill_md_path":"skills/cometchat-astro-patterns/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cometchat/cometchat-skills/tree/main/skills/cometchat-astro-patterns"},"layout":"multi","source":"github","category":"cometchat-skills","frontmatter":{"name":"cometchat-astro-patterns","license":"MIT","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.","compatibility":"Node.js >=18; React >=18; Astro >=3; @astrojs/react; @cometchat/chat-uikit-react ^6; @cometchat/chat-sdk-javascript ^4"},"skills_sh_url":"https://skills.sh/cometchat/cometchat-skills/cometchat-astro-patterns"},"updatedAt":"2026-05-18T19:04:48.989Z"}}