diff --git a/README.md b/README.md index d2fefa2..8aa59f1 100644 --- a/README.md +++ b/README.md @@ -618,3 +618,11 @@ import type { ## License MIT + +## Skill install (skills.sh) + +Want to reuse this SDK as a Codex/skills.sh skill? The skill lives at `skills/appgram-react-native-sdk`. + +- Local install from this repo: `npx skills add . --skill appgram-react-native-sdk` +- From GitHub: `npx skills add https://github.com// --skill appgram-react-native-sdk` +- Peer deps required in consuming projects: `@react-native-async-storage/async-storage`, `lucide-react-native`, `react-native-svg`, `react-native-markdown-display`, `react-native-render-html` (install after `@appgram/react-native`; run `npx pod-install` for iOS). diff --git a/skills/appgram-react-native-sdk/README.md b/skills/appgram-react-native-sdk/README.md new file mode 100644 index 0000000..84819bd --- /dev/null +++ b/skills/appgram-react-native-sdk/README.md @@ -0,0 +1,14 @@ +# Appgram React Native SDK skill (skills.sh) + +Use this to pull the SDK guidance into Codex/skills.sh. + +Install: +- From this repo root: `npx skills add . --skill appgram-react-native-sdk` +- From GitHub: `npx skills add https://github.com// --skill appgram-react-native-sdk` + +What’s inside: +- `SKILL.md` instructions for integrating/maintaining `@appgram/react-native` +- `agents/openai.yaml` display metadata +- references for hooks, components, API client, platform setup, and snippets + +Peer deps (for app integrators): `@react-native-async-storage/async-storage`, `lucide-react-native`, `react-native-svg`, `react-native-markdown-display`, `react-native-render-html` (run `npx pod-install` on iOS after installing). diff --git a/skills/appgram-react-native-sdk/SKILL.md b/skills/appgram-react-native-sdk/SKILL.md new file mode 100644 index 0000000..682a305 --- /dev/null +++ b/skills/appgram-react-native-sdk/SKILL.md @@ -0,0 +1,109 @@ +--- +name: appgram-react-native-sdk +description: "Integrate or maintain the Appgram React Native SDK (@appgram/react-native): install peers, configure AppgramProvider, use built-in components/hooks for feedback, roadmap, releases, help center, support, surveys, blog, status, chat, apply theming, and run build/lint/docs tasks." +--- + +# Appgram React Native SDK + +## When to use +- You need to embed Appgram feedback/roadmap/changelog/help/support/surveys/blog/status/chat in a React Native app. +- You are wiring Appgram headless hooks into custom UI. +- You are maintaining this SDK: build, lint, docs, publish, or debugging peer/native issues. + +## Prerequisites +- React Native ≥0.70, React 18, Metro; iOS requires CocoaPods, Android uses autolinking. +- Peer deps: `@react-native-async-storage/async-storage`, `lucide-react-native`, `react-native-svg`, `react-native-markdown-display`, `react-native-render-html`. +- Install peers after the SDK, then `npx pod-install` for iOS. + +## Quick start +- Install: `npm install @appgram/react-native` then peer deps `npm install @react-native-async-storage/async-storage lucide-react-native react-native-svg react-native-markdown-display react-native-render-html`. +- iOS: run `npx pod-install` after installing peers. +- Wrap your app once: +```tsx + + {children} + +``` +- Use ready UI or headless hooks: +```tsx + console.log(wish)} /> + Alert.alert('Sent')} /> +// Hooks +const { wishes, isLoading, refetch } = useWishes() +const { vote } = useVote() +``` + +## Feature map (components → hooks) +- Feedback: `WishList`, `WishCard`, `VoteButton`, `WishDetailModal`, `SubmitWishSheet` → `useWishes`, `useVote`, `useComments`. +- Roadmap: `RoadmapBoard` → `useRoadmap`. +- Releases/Changelog: `Releases`, `ReleaseList`, `ReleaseDetail` → `useReleases`, `useRelease`. +- Help Center: `HelpCenter`, `HelpFlowCard`, `HelpFlowDetail`, `HelpArticleCard`, `HelpArticleDetail` → `useHelpCenter`, `useHelpFlow`, `useHelpArticle`. +- Support & Forms: `SupportForm`, `FormRenderer` → `useSupport`, `useForm`, `useFormSubmit`. +- Surveys: `SurveyForm` → `useSurvey`, `useSurveySubmit`. +- Blog: `Blog`, `BlogList`, `BlogCard`, `BlogPostDetail` → `useBlogPosts`, `useBlogPost`, `useBlogCategories`, `useFeaturedPosts`. +- Status: `StatusBoard` → `useStatus`. +- Chat: `ChatScreen` (`ChatSource` type available) – pull data via context client as needed. +- Base UI bits: `Button`, `Card`, `Badge`, `Input` for consistent styling. + +**Hook pattern:** hooks return data + `isLoading` (and often `error`, `refetch`); many accept `refreshInterval` and filter props (see exported option/result types). + +### Props & options details +- Hooks: see `references/hooks.md` (options, return shapes, behaviors like refreshInterval and fingerprinting). +- Components: see `references/components.md` (purpose + key props per component). + +## Configuration & theming +- `AppgramProvider.config`: + - `projectId` (required), `orgSlug`/`projectSlug` for routing. + - `apiUrl` override for self-host/staging (default `https://api.appgram.dev`). + - `enableFingerprinting` (default true) uses AsyncStorage + device info for anonymous votes. + - `theme`: `mode` (`light`|`dark`|`system`), optional `lightColors`/`darkColors` partial overrides; defaults from Hazel design system. +- Access context: `useAppgramContext()` → `{ client, config, fingerprint, theme }`. +- Theming in custom UI: `useAppgramTheme()` → `{ colors, spacing, radius, typography, isDark, mode }`; palette exports `lightColors`, `darkColors`, scales `spacing`, `radius`, `typography` for reuse. + +### Sample themed usage +```tsx +const { colors, spacing, radius } = useAppgramTheme() +return ( + + + +) +``` + +## API client +- Get the instantiated `AppgramClient` from context: `const { client } = useAppgramContext()`. +- Methods mirror hooks (e.g., `client.getWishes`, `client.vote`, `client.getRoadmap`); responses follow `ApiResponse` / `PaginatedResponse` types exported from `types`. +- Use when you need imperative flows (e.g., prefetch before navigation) or custom caching. + +## Local development & maintenance (this repo) +- Install dev deps: `npm install`. +- Lint: `npm run lint`; typecheck: `npm run typecheck`. +- Build package: `npm run build` (builder-bob, outputs to `lib/`); runs automatically on `npm install` via `prepare`. +- Docs: `npm run docs:json` (typedoc) → `docs.json`; `npm run docs:transform` (uses `transform-docs.js`); `npm run docs:build` to do both. +- Publish (when ready): `npm run release` (assumes npm auth + version bump). Keep `react-native-builder-bob` config in `package.json`; build uses `tsconfig.build.json`. + +## Platform setup & debugging +- Install order, pod install, Gradle check, cache clears, and platform notes: `references/platform-setup.md`. + +## Common recipes +- **Custom vote button:** use `useVote`; pass `onVote` to sync local counts; guard for missing fingerprint by showing a prompt to enable cookies/storage. +- **Support with magic link:** use `useSupport`; call `requestMagicLink(email)` then `verifyToken(token)`; tickets also saved locally (`storedTickets`, `clearStoredTickets`). +- **Embed changelog tab:** stack navigator screen with `Releases`; on press, navigate to detail screen wrapping `ReleaseDetail`. +- **Anonymous wishlist:** keep `enableFingerprinting` on (default); if privacy requires, set false and disable voting UI. +- **Blog index + detail:** `BlogList` for landing; use `useBlogPosts` if you need infinite scroll; route to `BlogPostDetail` on press. +- **Status page banner:** call `useStatus({ slug, refreshInterval: 60000 })` and render a small inline banner with `data.status`. +- See ready-to-paste code in `references/snippets.md`. + +## Troubleshooting +- Missing peer deps / native linking: ensure all peers installed; run `npx pod-install` for iOS; clear Metro cache if symbols missing. +- Theming not applying: verify `theme.mode` not overridden by system; pass both light/dark overrides when customizing primary/background/foreground. +- Anonymous voting blocked: set `enableFingerprinting=false` if fingerprint cannot be generated, or ensure AsyncStorage works in environment. +- API errors: confirm `projectId`/slugs and `apiUrl`; use `client` methods to inspect `response.success` and `response.error`. +- Version support: built for React Native ≥0.70 and React 18+. Ensure `react-native-svg` and `lucide-react-native` versions stay compatible. + +## References (load on demand) +- Hooks options/returns: `references/hooks.md` +- Components props notes: `references/components.md` +- API client methods + notes: `references/api-client.md` +- Platform setup & debugging: `references/platform-setup.md` +- Practical code snippets: `references/snippets.md` diff --git a/skills/appgram-react-native-sdk/agents/openai.yaml b/skills/appgram-react-native-sdk/agents/openai.yaml new file mode 100644 index 0000000..4618a50 --- /dev/null +++ b/skills/appgram-react-native-sdk/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Appgram RN SDK" + short_description: "Use and maintain the Appgram React Native SDK." + default_prompt: "Use when integrating or maintaining @appgram/react-native: install peers, wrap AppgramProvider, pick components/hooks, theme overrides, and run lint/build/docs tasks." diff --git a/skills/appgram-react-native-sdk/references/api-client.md b/skills/appgram-react-native-sdk/references/api-client.md new file mode 100644 index 0000000..120611d --- /dev/null +++ b/skills/appgram-react-native-sdk/references/api-client.md @@ -0,0 +1,65 @@ +# AppgramClient reference (imperative API) + +Get client: `const { client } = useAppgramContext()`. Methods return `ApiResponse` unless noted. + +## Wishes +- `getPublicWishes(filters?: WishFilters)` → paginated wishes (transforms raw response). +- `getWish(wishId)` +- `createWish({ title, description, author_email?, author_name?, category_id? })` + +## Votes +- `checkVote(wishId, fingerprint)` +- `createVote(wishId, fingerprint, voterEmail?)` +- `deleteVote(voteId)` + +## Comments +- `getComments(wishId, { page?, per_page? })` → normalizes to `CommentsResponse`. +- `createComment({ wish_id, content, author_name?, author_email?, parent_id? })` + +## Roadmap +- `getRoadmapData()` (requires projectId; uses orgSlug/projectSlug when provided). + +## Releases / Changelog +- Needs both `orgSlug` and `projectSlug` in provider config. +- `getReleases({ limit? })` +- `getRelease(releaseSlug)` +- `getReleaseFeatures(releaseSlug)` + +## Help Center +- `getHelpCollection()` → `{ collection, flows }` +- `getHelpFlow(slug)` +- `getHelpArticle(slug, flowId)` + +## Support +- `uploadFile(file)` → size limit 10MB; returns `{ url, name, size, mime_type? }`. +- `submitSupportRequest(data: SupportRequestInput)`; auto-uploads attachments first. +- `sendSupportMagicLink(email)` +- `verifySupportToken(token)` → `{ tickets, user_email }` +- `getSupportTicket(ticketId, token)` +- `addSupportMessage(ticketId, token, content)` + +## Status +- `getPublicStatusOverview(slug = 'status')` + +## Surveys +- `getPublicSurvey(slug)` → includes `nodes`. +- `submitSurveyResponse(surveyId, data)` +- `getPublicSurveyCustomization(surveyId)` + +## Forms +- `getForm(formId)` → normalizes portal form shape. +- `trackFormView(formId)` → POST; tolerant of non-JSON responses. +- `submitForm(projectId, formId, data)` → tolerant of empty/non-JSON responses. + +## Page data +- `getPageData()` → combined public payload for landing usage. + +## Blog +- `getBlogPosts(filters?: BlogFilters)` → paginated transform. +- `getBlogPost(slug)` +- `getFeaturedBlogPosts()` +- `getBlogCategories()` +- `getBlogPostsByCategory(categorySlug, { page?, per_page? })` +- `getBlogPostsByTag(tag, { page?, per_page? })` +- `searchBlogPosts(query, { page?, per_page? })` +- `getRelatedBlogPosts(slug)` diff --git a/skills/appgram-react-native-sdk/references/components.md b/skills/appgram-react-native-sdk/references/components.md new file mode 100644 index 0000000..d3d6553 --- /dev/null +++ b/skills/appgram-react-native-sdk/references/components.md @@ -0,0 +1,47 @@ +# Components cheat sheet + +Use inside `AppgramProvider`. All components are stylable via theme exports or props where noted. + +## Feedback / Wishes +- `WishList`: full list + filters; props `title?`, `description?`, `filters?`, `showSubmitButton?`, `submitButtonText?`, callbacks `onWishPress`, `onWishSubmitted`, `onVote`, `onCommentPress`, `refreshInterval?`. +- `WishCard`: single wish card; props `wish`, `onPress`, `onVote`, `onCommentPress`. +- `VoteButton`: standalone vote UI; props `wishId`, `initialVoteCount`, `initialHasVoted`, `onVoteChange`. +- `WishDetailModal`: modal detail w/ comments & votes; props `wishId`, `visible`, `onClose`, `onVote`, `onComment`. +- `SubmitWishSheet`: sheet to create wish; props `visible`, `onClose`, `onSuccess`, `title?`, `description?`. + +## Roadmap +- `RoadmapBoard`: Kanban-style columns; props `onItemPress?`, `refreshInterval?`. + +## Releases / Changelog +- `Releases`: combined list + detail navigation; `onReleasePress?`. +- `ReleaseList`: list view; props `limit?`, `onReleasePress`. +- `ReleaseDetail`: single release; props `releaseSlug`, `onBack?`. + +## Help Center +- `HelpCenter`: flows + articles overview; props `title?`, `onFlowPress`, `onArticlePress`. +- `HelpFlowCard`: summary card; props `flow`, `onPress`. +- `HelpFlowDetail`: flow detail; props `slug`, `onArticlePress`, `onBack?`. +- `HelpArticleCard`: article preview; props `article`, `onPress`. +- `HelpArticleDetail`: article content; props `slug`, `flowId?`, `onBack?`. + +## Support & Forms +- `SupportForm`: ticket form; props `title?`, `userEmail?`, `userName?`, `onSuccess`, `onError?`. +- `FormRenderer`: render dynamic form by `formId`; props `formId`, `onSuccess?`, `onError?`. + +## Surveys +- `SurveyForm`: interactive survey; props `slug`, `onSuccess`, `onError?`. + +## Blog +- `Blog`: full blog view; props `title?`, `onPostPress`. +- `BlogList`: list with pagination/filter; props `category?`, `onPostPress`. +- `BlogCard`: card; props `post`, `onPress`. +- `BlogPostDetail`: article content; props `slug`, `onBack?`. + +## Status +- `StatusBoard`: status + incidents; props `slug`, `refreshInterval?`. + +## Chat +- `ChatScreen`: chat UI; uses `ChatSource` type; expects messages from Appgram client or custom source. + +## Base UI +- `Button`, `Card`, `Badge`, `Input`: Hazel-themed primitives; accept standard RN text/input props plus style overrides. diff --git a/skills/appgram-react-native-sdk/references/hooks.md b/skills/appgram-react-native-sdk/references/hooks.md new file mode 100644 index 0000000..090bba8 --- /dev/null +++ b/skills/appgram-react-native-sdk/references/hooks.md @@ -0,0 +1,58 @@ +# Hooks cheat sheet + +Each hook is headless: returns data, `isLoading`, often `error`, `refetch`, plus setters. Most support `refreshInterval` (ms) and `skip` for lazy load where provided. Use with `AppgramProvider` mounted. + +## Feedback / Wishes +- `useWishes(options?: { filters?: WishFilters; refreshInterval?: number; skip?: boolean })` + - Returns `{ wishes, total, page, totalPages, isLoading, error, setFilters(filters), setPage(page), refetch }` + - Includes fingerprint when voting enabled; `setFilters` resets page to 1. +- `useVote({ onVote?, onError? }?)` + - Returns `{ vote(wishId, currentCount), unvote(wishId, voteId, currentCount), checkVote(wishId), isVoting, error }` + - Requires `fingerprint` (default enabled); `checkVote` safe if missing. +- `useComments({ wishId, autoFetch?, refreshInterval? })` + - Returns `{ comments, isLoading, error, isSubmitting, addComment(body, name?, email?), refetch }` + +## Roadmap +- `useRoadmap({ refreshInterval? })` + - Returns `{ roadmap, columns, totalItems, isLoading, error, refetch }` + +## Releases / Changelog +- `useReleases({ limit?, page?, refreshInterval? })` + - Returns `{ releases, isLoading, error, page, totalPages, setPage, refetch }` +- `useRelease({ releaseSlug, refreshInterval? })` + - Returns `{ release, features, isLoading, error, refetch }` + +## Help Center +- `useHelpCenter()` → `{ collection, flows, isLoading, error }` +- `useHelpFlow(slug)` → `{ flow, isLoading, error }` +- `useHelpArticle(articleSlug, flowId?)` → `{ article, isLoading, error }` + +## Support & Forms +- `useSupport({ onSuccess?, onError? }?)` + - Returns submission + auth helpers: `{ submitTicket(data), isSubmitting, error, successMessage, clearMessages, requestMagicLink(email), isSendingMagicLink, verifyToken(token), isVerifying, storedTickets, loadStoredTickets, clearStoredTickets }` + - Stores last 50 tickets in AsyncStorage (fallback to in-memory). +- `useForm(formId, { refreshInterval?, skip? }?)` + - `{ form, isLoading, error, refetch }` +- `useFormSubmit({ onSuccess?, onError? }?)` + - `{ submitForm(projectId, formId, payload), isSubmitting, error }` + +## Surveys +- `useSurvey(slug, { refreshInterval?, skip? }?)` + - `{ survey, nodes, isLoading, error, refetch }` +- `useSurveySubmit({ onSuccess?, onError? }?)` + - `{ submitResponse(surveyId, payload), isSubmitting, error }` + +## Blog +- `useBlogPosts({ category?, per_page?, page?, search?, refreshInterval? }?)` + - `{ posts, page, totalPages, setPage, setFilters, isLoading, error, refetch }` +- `useBlogPost({ slug })` → `{ post, relatedPosts, isLoading, error }` +- `useBlogCategories()` → `{ categories, isLoading, error }` +- `useFeaturedPosts()` → `{ posts, isLoading, error }` + +## Status +- `useStatus({ slug, refreshInterval? })` + - `{ data, isLoading, error, refetch }` + +## Shared utilities +- `useAppgramContext()` → `{ client, config, fingerprint, theme }` +- `useAppgramTheme()` → `{ colors, spacing, radius, typography, isDark, mode }` diff --git a/skills/appgram-react-native-sdk/references/platform-setup.md b/skills/appgram-react-native-sdk/references/platform-setup.md new file mode 100644 index 0000000..3399a64 --- /dev/null +++ b/skills/appgram-react-native-sdk/references/platform-setup.md @@ -0,0 +1,36 @@ +# Platform setup & debugging + +## Install & link +1) `npm install @appgram/react-native` +2) `npm install @react-native-async-storage/async-storage lucide-react-native react-native-svg react-native-markdown-display react-native-render-html` +3) iOS: `npx pod-install` (or `cd ios && pod install`). +4) Android: open Android Studio/Gradle sync or run `cd android && ./gradlew :app:assembleDebug` once to verify linking. + +## Minimum versions +- React Native ≥0.70, React 18. +- react-native-svg ≥13, lucide-react-native ≥0.300. + +## Common fixes +- Metro cache: `npm start -- --reset-cache` +- Watchman: `watchman watch-del-all` (if installed) +- Android clean build: `cd android && ./gradlew clean && ./gradlew :app:assembleDebug` +- iOS clean pods: `cd ios && rm -rf Pods Podfile.lock && pod install` +- Hermes mismatch: ensure RN version default Hermes enabled; if disabling Hermes, rebuild pods. +- Missing SVG icons: reinstall `react-native-svg` and `lucide-react-native`, then rebuild pods / Gradle. + +## iOS notes +- If using Xcode, ensure the pods integrate with `use_frameworks!` defaults; no extra manual steps needed. +- For simulator fingerprinting issues, reset simulator content or clear AsyncStorage: `xcrun simctl erase all` (destructive) or uninstall the app. + +## Android notes +- Ensure `mavenCentral()` is present in `android/build.gradle`. +- If release build crashes on SVG, check ProGuard/R8 rules; typically not needed, but you can keep `-keep class com.horcrux.svg.** { *; }` as a safeguard. +- AsyncStorage failing on Android emulator: wipe data via AVD Manager or reinstall app. + +## Web (Expo / RNW) +- Components assume native; hooks can be used in Expo if dependencies are available; check markdown/html render libs compatibility on web. + +## Validation checklist before shipping +- Run `npm run lint` and `npm run typecheck`. +- Build once: `npm run build` to ensure bob output. +- For app integrators: verify one happy-path flow per feature (wishlist vote, support submit, survey submit, blog post view, status load) on both platforms. diff --git a/skills/appgram-react-native-sdk/references/snippets.md b/skills/appgram-react-native-sdk/references/snippets.md new file mode 100644 index 0000000..7ca5e92 --- /dev/null +++ b/skills/appgram-react-native-sdk/references/snippets.md @@ -0,0 +1,126 @@ +# Practical snippets + +## Stack with wishlist + detail + support +```tsx +function App() { + return ( + + + + + + + + + + ) +} + +function WishesScreen({ navigation }) { + return ( + navigation.navigate('WishDetail', { id: wish.id })} + onWishSubmitted={() => navigation.navigate('Support')} + /> + ) +} + +function WishDetailScreen({ route, navigation }) { + return ( + navigation.goBack()} + onComment={() => {}} + /> + ) +} + +function SupportScreen() { + return ( + Alert.alert('Sent')} + onError={(err) => Alert.alert('Error', err)} + /> + ) +} +``` + +## Custom vote button with hook +```tsx +function MiniVote({ wish }) { + const { vote, unvote, checkVote, isVoting } = useVote() + const [state, set] = useState({ has: wish.has_voted, count: wish.vote_count, id: undefined as string | undefined }) + + useEffect(() => { checkVote(wish.id).then(r => set(s => ({ ...s, has: r.hasVoted, id: r.voteId }))) }, [wish.id]) + + const toggle = async () => { + if (state.has && state.id) { + const ok = await unvote(wish.id, state.id, state.count) + if (ok) set(s => ({ ...s, has: false, count: Math.max(0, s.count - 1) })) + } else { + const ok = await vote(wish.id, state.count) + if (ok) set(s => ({ ...s, has: true, count: s.count + 1 })) + } + } + + return ( +