From 4e34270786e3c64eb1f4003704ef0960742731bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schober?= Date: Fri, 13 Mar 2026 06:23:06 +0100 Subject: [PATCH] initial commit --- .../better-auth-best-practices/SKILL.md | 183 + .agents/skills/building-native-ui/SKILL.md | 307 ++ .../references/animations.md | 189 + .../building-native-ui/references/controls.md | 245 ++ .../references/form-sheet.md | 251 ++ .../references/gradients.md | 116 + .../building-native-ui/references/icons.md | 218 ++ .../building-native-ui/references/media.md | 229 ++ .../references/route-structure.md | 229 ++ .../building-native-ui/references/search.md | 237 ++ .../building-native-ui/references/storage.md | 110 + .../building-native-ui/references/tabs.md | 417 +++ .../references/toolbar-and-headers.md | 267 ++ .../references/visual-effects.md | 195 + .../references/webgpu-three.md | 589 +++ .../references/zoom-transitions.md | 161 + .agents/skills/expo-cicd-workflows/SKILL.md | 92 + .../expo-cicd-workflows/scripts/fetch.js | 109 + .../expo-cicd-workflows/scripts/package.json | 11 + .../expo-cicd-workflows/scripts/validate.js | 84 + .agents/skills/expo-deployment/SKILL.md | 190 + .../references/app-store-metadata.md | 477 +++ .../references/ios-app-store.md | 357 ++ .../expo-deployment/references/play-store.md | 246 ++ .../expo-deployment/references/testflight.md | 59 + .../expo-deployment/references/workflows.md | 200 ++ .agents/skills/expo-dev-client/SKILL.md | 173 + .agents/skills/expo-tailwind-setup/SKILL.md | 456 +++ .agents/skills/heroui-native/LICENSE.txt | 21 + .agents/skills/heroui-native/SKILL.md | 243 ++ .../scripts/get_component_docs.mjs | 157 + .../skills/heroui-native/scripts/get_docs.mjs | 154 + .../heroui-native/scripts/get_theme.mjs | 222 ++ .../heroui-native/scripts/list_components.mjs | 134 + .agents/skills/hono/SKILL.md | 90 + .agents/skills/native-data-fetching/SKILL.md | 503 +++ .../references/expo-router-loaders.md | 344 ++ .agents/skills/shadcn/SKILL.md | 241 ++ .agents/skills/shadcn/agents/openai.yml | 5 + .agents/skills/shadcn/assets/shadcn-small.png | Bin 0 -> 1049 bytes .agents/skills/shadcn/assets/shadcn.png | Bin 0 -> 3852 bytes .agents/skills/shadcn/cli.md | 257 ++ .agents/skills/shadcn/customization.md | 203 ++ .agents/skills/shadcn/evals/evals.json | 47 + .agents/skills/shadcn/mcp.md | 94 + .agents/skills/shadcn/rules/base-vs-radix.md | 308 ++ .agents/skills/shadcn/rules/composition.md | 197 + .agents/skills/shadcn/rules/forms.md | 194 + .agents/skills/shadcn/rules/icons.md | 101 + .agents/skills/shadcn/rules/styling.md | 161 + .agents/skills/turborepo/SKILL.md | 914 +++++ .agents/skills/turborepo/command/turborepo.md | 70 + .../references/best-practices/RULE.md | 241 ++ .../references/best-practices/dependencies.md | 246 ++ .../references/best-practices/packages.md | 335 ++ .../references/best-practices/structure.md | 270 ++ .../turborepo/references/boundaries/RULE.md | 126 + .../turborepo/references/caching/RULE.md | 107 + .../turborepo/references/caching/gotchas.md | 169 + .../references/caching/remote-cache.md | 127 + .../skills/turborepo/references/ci/RULE.md | 79 + .../turborepo/references/ci/github-actions.md | 162 + .../turborepo/references/ci/patterns.md | 145 + .../skills/turborepo/references/ci/vercel.md | 103 + .../skills/turborepo/references/cli/RULE.md | 100 + .../turborepo/references/cli/commands.md | 297 ++ .../references/configuration/RULE.md | 211 ++ .../configuration/global-options.md | 187 + .../references/configuration/gotchas.md | 348 ++ .../references/configuration/tasks.md | 281 ++ .../turborepo/references/environment/RULE.md | 96 + .../references/environment/gotchas.md | 141 + .../turborepo/references/environment/modes.md | 101 + .../turborepo/references/filtering/RULE.md | 148 + .../references/filtering/patterns.md | 152 + .../skills/turborepo/references/watch/RULE.md | 99 + .agents/skills/upgrading-expo/SKILL.md | 134 + .../references/expo-av-to-audio.md | 144 + .../references/expo-av-to-video.md | 156 + .../upgrading-expo/references/native-tabs.md | 111 + .../references/new-architecture.md | 79 + .../upgrading-expo/references/react-19.md | 79 + .../references/react-compiler.md | 59 + .../vercel-composition-patterns/AGENTS.md | 917 +++++ .../vercel-composition-patterns/README.md | 60 + .../vercel-composition-patterns/SKILL.md | 88 + .../rules/architecture-avoid-boolean-props.md | 94 + .../rules/architecture-compound-components.md | 108 + .../patterns-children-over-render-props.md | 84 + .../rules/patterns-explicit-variants.md | 94 + .../rules/react19-no-forwardref.md | 42 + .../rules/state-context-interface.md | 191 + .../rules/state-decouple-implementation.md | 103 + .../rules/state-lift-state.md | 125 + .../vercel-react-best-practices/AGENTS.md | 3185 +++++++++++++++++ .../vercel-react-best-practices/README.md | 127 + .../vercel-react-best-practices/SKILL.md | 143 + .../rules/advanced-event-handler-refs.md | 55 + .../rules/advanced-init-once.md | 42 + .../rules/advanced-use-latest.md | 39 + .../rules/async-api-routes.md | 35 + .../rules/async-defer-await.md | 80 + .../rules/async-dependencies.md | 48 + .../rules/async-parallel.md | 24 + .../rules/async-suspense-boundaries.md | 99 + .../rules/bundle-barrel-imports.md | 59 + .../rules/bundle-conditional.md | 37 + .../rules/bundle-defer-third-party.md | 48 + .../rules/bundle-dynamic-imports.md | 34 + .../rules/bundle-preload.md | 44 + .../rules/client-event-listeners.md | 78 + .../rules/client-localstorage-schema.md | 74 + .../rules/client-passive-event-listeners.md | 48 + .../rules/client-swr-dedup.md | 56 + .../rules/js-batch-dom-css.md | 110 + .../rules/js-cache-function-results.md | 80 + .../rules/js-cache-property-access.md | 28 + .../rules/js-cache-storage.md | 68 + .../rules/js-combine-iterations.md | 32 + .../rules/js-early-exit.md | 50 + .../rules/js-flatmap-filter.md | 51 + .../rules/js-hoist-regexp.md | 45 + .../rules/js-index-maps.md | 37 + .../rules/js-length-check-first.md | 50 + .../rules/js-min-max-loop.md | 82 + .../rules/js-set-map-lookups.md | 24 + .../rules/js-tosorted-immutable.md | 57 + .../rules/rendering-activity.md | 26 + .../rules/rendering-animate-svg-wrapper.md | 38 + .../rules/rendering-conditional-render.md | 32 + .../rules/rendering-content-visibility.md | 38 + .../rules/rendering-hoist-jsx.md | 36 + .../rules/rendering-hydration-no-flicker.md | 72 + .../rendering-hydration-suppress-warning.md | 26 + .../rules/rendering-resource-hints.md | 85 + .../rules/rendering-script-defer-async.md | 68 + .../rules/rendering-svg-precision.md | 28 + .../rules/rendering-usetransition-loading.md | 75 + .../rules/rerender-defer-reads.md | 39 + .../rules/rerender-dependencies.md | 45 + .../rules/rerender-derived-state-no-effect.md | 40 + .../rules/rerender-derived-state.md | 29 + .../rules/rerender-functional-setstate.md | 77 + .../rules/rerender-lazy-state-init.md | 56 + .../rules/rerender-memo-with-default-value.md | 36 + .../rules/rerender-memo.md | 44 + .../rules/rerender-move-effect-to-event.md | 45 + .../rules/rerender-no-inline-components.md | 75 + .../rerender-simple-expression-in-memo.md | 35 + .../rules/rerender-transitions.md | 40 + .../rerender-use-ref-transient-values.md | 73 + .../rules/server-after-nonblocking.md | 73 + .../rules/server-auth-actions.md | 96 + .../rules/server-cache-lru.md | 41 + .../rules/server-cache-react.md | 76 + .../rules/server-dedup-props.md | 65 + .../rules/server-hoist-static-io.md | 136 + .../rules/server-parallel-fetching.md | 83 + .../rules/server-serialization.md | 38 + .../vercel-react-native-skills/AGENTS.md | 2864 +++++++++++++++ .../vercel-react-native-skills/README.md | 165 + .../vercel-react-native-skills/SKILL.md | 120 + .../rules/animation-derived-value.md | 53 + .../rules/animation-gesture-detector-press.md | 89 + .../rules/animation-gpu-properties.md | 61 + .../design-system-compound-components.md | 66 + .../rules/fonts-config-plugin.md | 71 + .../rules/imports-design-system-folder.md | 66 + .../rules/js-hoist-intl.md | 61 + .../rules/list-performance-callbacks.md | 44 + .../list-performance-function-references.md | 132 + .../rules/list-performance-images.md | 50 + .../rules/list-performance-inline-objects.md | 97 + .../rules/list-performance-item-expensive.md | 91 + .../rules/list-performance-item-memo.md | 82 + .../rules/list-performance-item-types.md | 100 + .../rules/list-performance-virtualize.md | 67 + .../rules/monorepo-native-deps-in-app.md | 46 + .../monorepo-single-dependency-versions.md | 63 + .../rules/navigation-native-navigators.md | 188 + .../react-compiler-destructure-functions.md | 50 + ...react-compiler-reanimated-shared-values.md | 48 + .../rules/react-state-dispatcher.md | 91 + .../rules/react-state-fallback.md | 56 + .../rules/react-state-minimize.md | 65 + .../rules/rendering-no-falsy-and.md | 74 + .../rules/rendering-text-in-text-component.md | 36 + .../rules/scroll-position-no-state.md | 71 + .../rules/state-ground-truth.md | 80 + .../rules/ui-expo-image.md | 61 + .../rules/ui-image-gallery.md | 103 + .../rules/ui-measure-views.md | 78 + .../rules/ui-menus.md | 170 + .../rules/ui-native-modals.md | 77 + .../rules/ui-pressable.md | 61 + .../rules/ui-safe-area-scroll.md | 65 + .../rules/ui-scrollview-content-inset.md | 43 + .../rules/ui-styling.md | 87 + .agents/skills/web-design-guidelines/SKILL.md | 40 + .claude/skills/better-auth-best-practices | 1 + .claude/skills/building-native-ui | 1 + .claude/skills/expo-cicd-workflows | 1 + .claude/skills/expo-deployment | 1 + .claude/skills/expo-dev-client | 1 + .claude/skills/expo-tailwind-setup | 1 + .claude/skills/heroui-native | 1 + .claude/skills/hono | 1 + .claude/skills/native-data-fetching | 1 + .claude/skills/shadcn | 1 + .claude/skills/turborepo | 1 + .claude/skills/upgrading-expo | 1 + .claude/skills/vercel-composition-patterns | 1 + .claude/skills/vercel-react-best-practices | 1 + .claude/skills/vercel-react-native-skills | 1 + .claude/skills/web-design-guidelines | 1 + .gitignore | 51 + .mcp.json | 24 + README.md | 102 + apps/native/.gitignore | 21 + apps/native/app.json | 17 + apps/native/app/(drawer)/(tabs)/_layout.tsx | 46 + apps/native/app/(drawer)/(tabs)/index.tsx | 16 + apps/native/app/(drawer)/(tabs)/two.tsx | 16 + apps/native/app/(drawer)/_layout.tsx | 72 + apps/native/app/(drawer)/index.tsx | 49 + apps/native/app/+not-found.tsx | 27 + apps/native/app/_layout.tsx | 34 + apps/native/app/modal.tsx | 37 + .../assets/images/android-icon-background.png | Bin 0 -> 17549 bytes .../assets/images/android-icon-foreground.png | Bin 0 -> 78796 bytes .../assets/images/android-icon-monochrome.png | Bin 0 -> 4140 bytes apps/native/assets/images/favicon.png | Bin 0 -> 1129 bytes apps/native/assets/images/icon.png | Bin 0 -> 393493 bytes .../assets/images/partial-react-logo.png | Bin 0 -> 5075 bytes apps/native/assets/images/react-logo.png | Bin 0 -> 6341 bytes apps/native/assets/images/react-logo@2x.png | Bin 0 -> 14225 bytes apps/native/assets/images/react-logo@3x.png | Bin 0 -> 21252 bytes apps/native/assets/images/splash-icon.png | Bin 0 -> 17547 bytes apps/native/components/container.tsx | 46 + apps/native/components/sign-in.tsx | 166 + apps/native/components/sign-up.tsx | 190 + apps/native/components/theme-toggle.tsx | 35 + apps/native/contexts/app-theme-context.tsx | 55 + apps/native/global.css | 5 + apps/native/lib/auth-client.ts | 16 + apps/native/metro.config.js | 13 + apps/native/package.json | 59 + apps/native/tsconfig.json | 11 + apps/server/.gitignore | 55 + apps/server/package.json | 27 + apps/server/src/index.ts | 26 + apps/server/tsconfig.json | 13 + apps/server/tsdown.config.ts | 9 + apps/web/.gitignore | 60 + apps/web/components.json | 24 + apps/web/index.html | 13 + apps/web/package.json | 43 + apps/web/src/components/header.tsx | 32 + apps/web/src/components/loader.tsx | 9 + apps/web/src/components/mode-toggle.tsx | 29 + apps/web/src/components/sign-in-form.tsx | 135 + apps/web/src/components/sign-up-form.tsx | 160 + apps/web/src/components/theme-provider.tsx | 11 + apps/web/src/components/user-menu.tsx | 62 + apps/web/src/index.css | 1 + apps/web/src/lib/auth-client.ts | 6 + apps/web/src/main.tsx | 29 + apps/web/src/routes/__root.tsx | 52 + apps/web/src/routes/dashboard.tsx | 28 + apps/web/src/routes/index.tsx | 34 + apps/web/src/routes/login.tsx | 19 + apps/web/tsconfig.json | 19 + apps/web/vite.config.ts | 18 + bts.jsonc | 31 + bun.lock | 3085 ++++++++++++++++ opencode.json | 32 + package.json | 52 + packages/auth/.gitignore | 34 + packages/auth/package.json | 25 + packages/auth/src/index.ts | 34 + packages/auth/tsconfig.json | 10 + packages/config/package.json | 5 + packages/config/tsconfig.base.json | 22 + packages/db/.gitignore | 35 + packages/db/docker-compose.yml | 23 + packages/db/drizzle.config.ts | 15 + packages/db/package.json | 35 + packages/db/src/index.ts | 6 + packages/db/src/schema/auth.ts | 93 + packages/db/src/schema/index.ts | 2 + packages/db/tsconfig.json | 10 + packages/env/package.json | 21 + packages/env/src/native.ts | 11 + packages/env/src/server.ts | 15 + packages/env/src/web.ts | 11 + packages/env/tsconfig.json | 3 + packages/ui/components.json | 24 + packages/ui/package.json | 36 + packages/ui/postcss.config.mjs | 5 + packages/ui/src/components/button.tsx | 55 + packages/ui/src/components/card.tsx | 88 + packages/ui/src/components/checkbox.tsx | 27 + packages/ui/src/components/dropdown-menu.tsx | 254 ++ packages/ui/src/components/input.tsx | 19 + packages/ui/src/components/label.tsx | 17 + packages/ui/src/components/skeleton.tsx | 13 + packages/ui/src/components/sonner.tsx | 45 + packages/ui/src/hooks/.gitkeep | 0 packages/ui/src/lib/utils.ts | 6 + packages/ui/src/styles/globals.css | 130 + packages/ui/tsconfig.json | 13 + skills-lock.json | 85 + tsconfig.json | 3 + turbo.json | 49 + 314 files changed, 37280 insertions(+) create mode 100644 .agents/skills/better-auth-best-practices/SKILL.md create mode 100644 .agents/skills/building-native-ui/SKILL.md create mode 100644 .agents/skills/building-native-ui/references/animations.md create mode 100644 .agents/skills/building-native-ui/references/controls.md create mode 100644 .agents/skills/building-native-ui/references/form-sheet.md create mode 100644 .agents/skills/building-native-ui/references/gradients.md create mode 100644 .agents/skills/building-native-ui/references/icons.md create mode 100644 .agents/skills/building-native-ui/references/media.md create mode 100644 .agents/skills/building-native-ui/references/route-structure.md create mode 100644 .agents/skills/building-native-ui/references/search.md create mode 100644 .agents/skills/building-native-ui/references/storage.md create mode 100644 .agents/skills/building-native-ui/references/tabs.md create mode 100644 .agents/skills/building-native-ui/references/toolbar-and-headers.md create mode 100644 .agents/skills/building-native-ui/references/visual-effects.md create mode 100644 .agents/skills/building-native-ui/references/webgpu-three.md create mode 100644 .agents/skills/building-native-ui/references/zoom-transitions.md create mode 100644 .agents/skills/expo-cicd-workflows/SKILL.md create mode 100644 .agents/skills/expo-cicd-workflows/scripts/fetch.js create mode 100644 .agents/skills/expo-cicd-workflows/scripts/package.json create mode 100644 .agents/skills/expo-cicd-workflows/scripts/validate.js create mode 100644 .agents/skills/expo-deployment/SKILL.md create mode 100644 .agents/skills/expo-deployment/references/app-store-metadata.md create mode 100644 .agents/skills/expo-deployment/references/ios-app-store.md create mode 100644 .agents/skills/expo-deployment/references/play-store.md create mode 100644 .agents/skills/expo-deployment/references/testflight.md create mode 100644 .agents/skills/expo-deployment/references/workflows.md create mode 100644 .agents/skills/expo-dev-client/SKILL.md create mode 100644 .agents/skills/expo-tailwind-setup/SKILL.md create mode 100644 .agents/skills/heroui-native/LICENSE.txt create mode 100644 .agents/skills/heroui-native/SKILL.md create mode 100755 .agents/skills/heroui-native/scripts/get_component_docs.mjs create mode 100755 .agents/skills/heroui-native/scripts/get_docs.mjs create mode 100755 .agents/skills/heroui-native/scripts/get_theme.mjs create mode 100755 .agents/skills/heroui-native/scripts/list_components.mjs create mode 100644 .agents/skills/hono/SKILL.md create mode 100644 .agents/skills/native-data-fetching/SKILL.md create mode 100644 .agents/skills/native-data-fetching/references/expo-router-loaders.md create mode 100644 .agents/skills/shadcn/SKILL.md create mode 100644 .agents/skills/shadcn/agents/openai.yml create mode 100644 .agents/skills/shadcn/assets/shadcn-small.png create mode 100644 .agents/skills/shadcn/assets/shadcn.png create mode 100644 .agents/skills/shadcn/cli.md create mode 100644 .agents/skills/shadcn/customization.md create mode 100644 .agents/skills/shadcn/evals/evals.json create mode 100644 .agents/skills/shadcn/mcp.md create mode 100644 .agents/skills/shadcn/rules/base-vs-radix.md create mode 100644 .agents/skills/shadcn/rules/composition.md create mode 100644 .agents/skills/shadcn/rules/forms.md create mode 100644 .agents/skills/shadcn/rules/icons.md create mode 100644 .agents/skills/shadcn/rules/styling.md create mode 100644 .agents/skills/turborepo/SKILL.md create mode 100644 .agents/skills/turborepo/command/turborepo.md create mode 100644 .agents/skills/turborepo/references/best-practices/RULE.md create mode 100644 .agents/skills/turborepo/references/best-practices/dependencies.md create mode 100644 .agents/skills/turborepo/references/best-practices/packages.md create mode 100644 .agents/skills/turborepo/references/best-practices/structure.md create mode 100644 .agents/skills/turborepo/references/boundaries/RULE.md create mode 100644 .agents/skills/turborepo/references/caching/RULE.md create mode 100644 .agents/skills/turborepo/references/caching/gotchas.md create mode 100644 .agents/skills/turborepo/references/caching/remote-cache.md create mode 100644 .agents/skills/turborepo/references/ci/RULE.md create mode 100644 .agents/skills/turborepo/references/ci/github-actions.md create mode 100644 .agents/skills/turborepo/references/ci/patterns.md create mode 100644 .agents/skills/turborepo/references/ci/vercel.md create mode 100644 .agents/skills/turborepo/references/cli/RULE.md create mode 100644 .agents/skills/turborepo/references/cli/commands.md create mode 100644 .agents/skills/turborepo/references/configuration/RULE.md create mode 100644 .agents/skills/turborepo/references/configuration/global-options.md create mode 100644 .agents/skills/turborepo/references/configuration/gotchas.md create mode 100644 .agents/skills/turborepo/references/configuration/tasks.md create mode 100644 .agents/skills/turborepo/references/environment/RULE.md create mode 100644 .agents/skills/turborepo/references/environment/gotchas.md create mode 100644 .agents/skills/turborepo/references/environment/modes.md create mode 100644 .agents/skills/turborepo/references/filtering/RULE.md create mode 100644 .agents/skills/turborepo/references/filtering/patterns.md create mode 100644 .agents/skills/turborepo/references/watch/RULE.md create mode 100644 .agents/skills/upgrading-expo/SKILL.md create mode 100644 .agents/skills/upgrading-expo/references/expo-av-to-audio.md create mode 100644 .agents/skills/upgrading-expo/references/expo-av-to-video.md create mode 100644 .agents/skills/upgrading-expo/references/native-tabs.md create mode 100644 .agents/skills/upgrading-expo/references/new-architecture.md create mode 100644 .agents/skills/upgrading-expo/references/react-19.md create mode 100644 .agents/skills/upgrading-expo/references/react-compiler.md create mode 100644 .agents/skills/vercel-composition-patterns/AGENTS.md create mode 100644 .agents/skills/vercel-composition-patterns/README.md create mode 100644 .agents/skills/vercel-composition-patterns/SKILL.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/state-context-interface.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md create mode 100644 .agents/skills/vercel-composition-patterns/rules/state-lift-state.md create mode 100644 .agents/skills/vercel-react-best-practices/AGENTS.md create mode 100644 .agents/skills/vercel-react-best-practices/README.md create mode 100644 .agents/skills/vercel-react-best-practices/SKILL.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-init-once.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-api-routes.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-defer-await.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-dependencies.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-parallel.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-conditional.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-preload.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-event-listeners.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-storage.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-early-exit.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-flatmap-filter.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-index-maps.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-length-check-first.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-activity.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-resource-hints.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-memo.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-transitions.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-auth-actions.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-cache-lru.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-cache-react.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-dedup-props.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md create mode 100644 .agents/skills/vercel-react-best-practices/rules/server-serialization.md create mode 100644 .agents/skills/vercel-react-native-skills/AGENTS.md create mode 100644 .agents/skills/vercel-react-native-skills/README.md create mode 100644 .agents/skills/vercel-react-native-skills/SKILL.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/animation-derived-value.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/animation-gesture-detector-press.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/animation-gpu-properties.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/design-system-compound-components.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/fonts-config-plugin.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/imports-design-system-folder.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/js-hoist-intl.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-callbacks.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-function-references.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-images.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-inline-objects.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-item-expensive.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-item-memo.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-item-types.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/list-performance-virtualize.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/monorepo-native-deps-in-app.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/monorepo-single-dependency-versions.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/navigation-native-navigators.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/react-compiler-destructure-functions.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/react-compiler-reanimated-shared-values.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/react-state-dispatcher.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/react-state-fallback.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/react-state-minimize.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/rendering-no-falsy-and.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/rendering-text-in-text-component.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/scroll-position-no-state.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/state-ground-truth.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-expo-image.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-image-gallery.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-measure-views.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-menus.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-native-modals.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-pressable.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-safe-area-scroll.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-scrollview-content-inset.md create mode 100644 .agents/skills/vercel-react-native-skills/rules/ui-styling.md create mode 100644 .agents/skills/web-design-guidelines/SKILL.md create mode 120000 .claude/skills/better-auth-best-practices create mode 120000 .claude/skills/building-native-ui create mode 120000 .claude/skills/expo-cicd-workflows create mode 120000 .claude/skills/expo-deployment create mode 120000 .claude/skills/expo-dev-client create mode 120000 .claude/skills/expo-tailwind-setup create mode 120000 .claude/skills/heroui-native create mode 120000 .claude/skills/hono create mode 120000 .claude/skills/native-data-fetching create mode 120000 .claude/skills/shadcn create mode 120000 .claude/skills/turborepo create mode 120000 .claude/skills/upgrading-expo create mode 120000 .claude/skills/vercel-composition-patterns create mode 120000 .claude/skills/vercel-react-best-practices create mode 120000 .claude/skills/vercel-react-native-skills create mode 120000 .claude/skills/web-design-guidelines create mode 100644 .gitignore create mode 100644 .mcp.json create mode 100644 README.md create mode 100644 apps/native/.gitignore create mode 100644 apps/native/app.json create mode 100644 apps/native/app/(drawer)/(tabs)/_layout.tsx create mode 100644 apps/native/app/(drawer)/(tabs)/index.tsx create mode 100644 apps/native/app/(drawer)/(tabs)/two.tsx create mode 100644 apps/native/app/(drawer)/_layout.tsx create mode 100644 apps/native/app/(drawer)/index.tsx create mode 100644 apps/native/app/+not-found.tsx create mode 100644 apps/native/app/_layout.tsx create mode 100644 apps/native/app/modal.tsx create mode 100644 apps/native/assets/images/android-icon-background.png create mode 100644 apps/native/assets/images/android-icon-foreground.png create mode 100644 apps/native/assets/images/android-icon-monochrome.png create mode 100644 apps/native/assets/images/favicon.png create mode 100644 apps/native/assets/images/icon.png create mode 100644 apps/native/assets/images/partial-react-logo.png create mode 100644 apps/native/assets/images/react-logo.png create mode 100644 apps/native/assets/images/react-logo@2x.png create mode 100644 apps/native/assets/images/react-logo@3x.png create mode 100644 apps/native/assets/images/splash-icon.png create mode 100644 apps/native/components/container.tsx create mode 100644 apps/native/components/sign-in.tsx create mode 100644 apps/native/components/sign-up.tsx create mode 100644 apps/native/components/theme-toggle.tsx create mode 100644 apps/native/contexts/app-theme-context.tsx create mode 100644 apps/native/global.css create mode 100644 apps/native/lib/auth-client.ts create mode 100644 apps/native/metro.config.js create mode 100644 apps/native/package.json create mode 100644 apps/native/tsconfig.json create mode 100644 apps/server/.gitignore create mode 100644 apps/server/package.json create mode 100644 apps/server/src/index.ts create mode 100644 apps/server/tsconfig.json create mode 100644 apps/server/tsdown.config.ts create mode 100644 apps/web/.gitignore create mode 100644 apps/web/components.json create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/src/components/header.tsx create mode 100644 apps/web/src/components/loader.tsx create mode 100644 apps/web/src/components/mode-toggle.tsx create mode 100644 apps/web/src/components/sign-in-form.tsx create mode 100644 apps/web/src/components/sign-up-form.tsx create mode 100644 apps/web/src/components/theme-provider.tsx create mode 100644 apps/web/src/components/user-menu.tsx create mode 100644 apps/web/src/index.css create mode 100644 apps/web/src/lib/auth-client.ts create mode 100644 apps/web/src/main.tsx create mode 100644 apps/web/src/routes/__root.tsx create mode 100644 apps/web/src/routes/dashboard.tsx create mode 100644 apps/web/src/routes/index.tsx create mode 100644 apps/web/src/routes/login.tsx create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/vite.config.ts create mode 100644 bts.jsonc create mode 100644 bun.lock create mode 100644 opencode.json create mode 100644 package.json create mode 100644 packages/auth/.gitignore create mode 100644 packages/auth/package.json create mode 100644 packages/auth/src/index.ts create mode 100644 packages/auth/tsconfig.json create mode 100644 packages/config/package.json create mode 100644 packages/config/tsconfig.base.json create mode 100644 packages/db/.gitignore create mode 100644 packages/db/docker-compose.yml create mode 100644 packages/db/drizzle.config.ts create mode 100644 packages/db/package.json create mode 100644 packages/db/src/index.ts create mode 100644 packages/db/src/schema/auth.ts create mode 100644 packages/db/src/schema/index.ts create mode 100644 packages/db/tsconfig.json create mode 100644 packages/env/package.json create mode 100644 packages/env/src/native.ts create mode 100644 packages/env/src/server.ts create mode 100644 packages/env/src/web.ts create mode 100644 packages/env/tsconfig.json create mode 100644 packages/ui/components.json create mode 100644 packages/ui/package.json create mode 100644 packages/ui/postcss.config.mjs create mode 100644 packages/ui/src/components/button.tsx create mode 100644 packages/ui/src/components/card.tsx create mode 100644 packages/ui/src/components/checkbox.tsx create mode 100644 packages/ui/src/components/dropdown-menu.tsx create mode 100644 packages/ui/src/components/input.tsx create mode 100644 packages/ui/src/components/label.tsx create mode 100644 packages/ui/src/components/skeleton.tsx create mode 100644 packages/ui/src/components/sonner.tsx create mode 100644 packages/ui/src/hooks/.gitkeep create mode 100644 packages/ui/src/lib/utils.ts create mode 100644 packages/ui/src/styles/globals.css create mode 100644 packages/ui/tsconfig.json create mode 100644 skills-lock.json create mode 100644 tsconfig.json create mode 100644 turbo.json diff --git a/.agents/skills/better-auth-best-practices/SKILL.md b/.agents/skills/better-auth-best-practices/SKILL.md new file mode 100644 index 0000000..fabd32f --- /dev/null +++ b/.agents/skills/better-auth-best-practices/SKILL.md @@ -0,0 +1,183 @@ +--- +name: better-auth-best-practices +description: Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables. Use when users mention Better Auth, betterauth, auth.ts, or need to set up TypeScript authentication with email/password, OAuth, or plugin configuration. +--- + +# Better Auth Integration Guide + +**Always consult [better-auth.com/docs](https://better-auth.com/docs) for code examples and latest API.** + +--- + +## Setup Workflow + +1. Install: `npm install better-auth` +2. Set env vars: `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` +3. Create `auth.ts` with database + config +4. Create route handler for your framework +5. Run `npx @better-auth/cli@latest migrate` +6. Verify: call `GET /api/auth/ok` — should return `{ status: "ok" }` + +--- + +## Quick Reference + +### Environment Variables + +- `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32` +- `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`) + +Only define `baseURL`/`secret` in config if env vars are NOT set. + +### File Location + +CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path. + +### CLI Commands + +- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter) +- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle +- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools + +**Re-run after adding/changing plugins.** + +--- + +## Core Config Options + +| Option | Notes | +| ------------------ | ---------------------------------------------- | +| `appName` | Optional display name | +| `baseURL` | Only if `BETTER_AUTH_URL` not set | +| `basePath` | Default `/api/auth`. Set `/` for root. | +| `secret` | Only if `BETTER_AUTH_SECRET` not set | +| `database` | Required for most features. See adapters docs. | +| `secondaryStorage` | Redis/KV for sessions & rate limits | +| `emailAndPassword` | `{ enabled: true }` to activate | +| `socialProviders` | `{ google: { clientId, clientSecret }, ... }` | +| `plugins` | Array of plugins | +| `trustedOrigins` | CSRF whitelist | + +--- + +## Database + +**Direct connections:** Pass `pg.Pool`, `mysql2` pool, `better-sqlite3`, or `bun:sqlite` instance. + +**ORM adapters:** Import from `better-auth/adapters/drizzle`, `better-auth/adapters/prisma`, `better-auth/adapters/mongodb`. + +**Critical:** Better Auth uses adapter model names, NOT underlying table names. If Prisma model is `User` mapping to table `users`, use `modelName: "user"` (Prisma reference), not `"users"`. + +--- + +## Session Management + +**Storage priority:** + +1. If `secondaryStorage` defined → sessions go there (not DB) +2. Set `session.storeSessionInDatabase: true` to also persist to DB +3. No database + `cookieCache` → fully stateless mode + +**Cookie cache strategies:** + +- `compact` (default) - Base64url + HMAC. Smallest. +- `jwt` - Standard JWT. Readable but signed. +- `jwe` - Encrypted. Maximum security. + +**Key options:** `session.expiresIn` (default 7 days), `session.updateAge` (refresh interval), `session.cookieCache.maxAge`, `session.cookieCache.version` (change to invalidate all sessions). + +--- + +## User & Account Config + +**User:** `user.modelName`, `user.fields` (column mapping), `user.additionalFields`, `user.changeEmail.enabled` (disabled by default), `user.deleteUser.enabled` (disabled by default). + +**Account:** `account.modelName`, `account.accountLinking.enabled`, `account.storeAccountCookie` (for stateless OAuth). + +**Required for registration:** `email` and `name` fields. + +--- + +## Email Flows + +- `emailVerification.sendVerificationEmail` - Must be defined for verification to work +- `emailVerification.sendOnSignUp` / `sendOnSignIn` - Auto-send triggers +- `emailAndPassword.sendResetPassword` - Password reset email handler + +--- + +## Security + +**In `advanced`:** + +- `useSecureCookies` - Force HTTPS cookies +- `disableCSRFCheck` - ⚠️ Security risk +- `disableOriginCheck` - ⚠️ Security risk +- `crossSubDomainCookies.enabled` - Share cookies across subdomains +- `ipAddress.ipAddressHeaders` - Custom IP headers for proxies +- `database.generateId` - Custom ID generation or `"serial"`/`"uuid"`/`false` + +**Rate limiting:** `rateLimit.enabled`, `rateLimit.window`, `rateLimit.max`, `rateLimit.storage` ("memory" | "database" | "secondary-storage"). + +--- + +## Hooks + +**Endpoint hooks:** `hooks.before` / `hooks.after` - Array of `{ matcher, handler }`. Use `createAuthMiddleware`. Access `ctx.path`, `ctx.context.returned` (after), `ctx.context.session`. + +**Database hooks:** `databaseHooks.user.create.before/after`, same for `session`, `account`. Useful for adding default values or post-creation actions. + +**Hook context (`ctx.context`):** `session`, `secret`, `authCookies`, `password.hash()`/`verify()`, `adapter`, `internalAdapter`, `generateId()`, `tables`, `baseURL`. + +--- + +## Plugins + +**Import from dedicated paths for tree-shaking:** + +``` +import { twoFactor } from "better-auth/plugins/two-factor" +``` + +NOT `from "better-auth/plugins"`. + +**Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`. + +Client plugins go in `createAuthClient({ plugins: [...] })`. + +--- + +## Client + +Import from: `better-auth/client` (vanilla), `better-auth/react`, `better-auth/vue`, `better-auth/svelte`, `better-auth/solid`. + +Key methods: `signUp.email()`, `signIn.email()`, `signIn.social()`, `signOut()`, `useSession()`, `getSession()`, `revokeSession()`, `revokeSessions()`. + +--- + +## Type Safety + +Infer types: `typeof auth.$Infer.Session`, `typeof auth.$Infer.Session.user`. + +For separate client/server projects: `createAuthClient()`. + +--- + +## Common Gotchas + +1. **Model vs table name** - Config uses ORM model name, not DB table name +2. **Plugin schema** - Re-run CLI after adding plugins +3. **Secondary storage** - Sessions go there by default, not DB +4. **Cookie cache** - Custom session fields NOT cached, always re-fetched +5. **Stateless mode** - No DB = session in cookie only, logout on cache expiry +6. **Change email flow** - Sends to current email first, then new email + +--- + +## Resources + +- [Docs](https://better-auth.com/docs) +- [Options Reference](https://better-auth.com/docs/reference/options) +- [LLMs.txt](https://better-auth.com/llms.txt) +- [GitHub](https://github.com/better-auth/better-auth) +- [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts) diff --git a/.agents/skills/building-native-ui/SKILL.md b/.agents/skills/building-native-ui/SKILL.md new file mode 100644 index 0000000..a5b94ff --- /dev/null +++ b/.agents/skills/building-native-ui/SKILL.md @@ -0,0 +1,307 @@ +--- +name: building-native-ui +description: Complete guide for building beautiful apps with Expo Router. Covers fundamentals, styling, components, navigation, animations, patterns, and native tabs. +version: 1.0.1 +license: MIT +--- + +# Expo UI Guidelines + +## References + +Consult these resources as needed: + +``` +references/ + animations.md Reanimated: entering, exiting, layout, scroll-driven, gestures + controls.md Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker + form-sheet.md Form sheets in expo-router: configuration, footers and background interaction. + gradients.md CSS gradients via experimental_backgroundImage (New Arch only) + icons.md SF Symbols via expo-image (sf: source), names, animations, weights + media.md Camera, audio, video, and file saving + route-structure.md Route conventions, dynamic routes, groups, folder organization + search.md Search bar with headers, useSearch hook, filtering patterns + storage.md SQLite, AsyncStorage, SecureStore + tabs.md NativeTabs, migration from JS tabs, iOS 26 features + toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only) + visual-effects.md Blur (expo-blur) and liquid glass (expo-glass-effect) + webgpu-three.md 3D graphics, games, GPU visualizations with WebGPU and Three.js + zoom-transitions.md Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+) +``` + +## Running the App + +**CRITICAL: Always try Expo Go first before creating custom builds.** + +Most Expo apps work in Expo Go without any custom native code. Before running `npx expo run:ios` or `npx expo run:android`: + +1. **Start with Expo Go**: Run `npx expo start` and scan the QR code with Expo Go +2. **Check if features work**: Test your app thoroughly in Expo Go +3. **Only create custom builds when required** - see below + +### When Custom Builds Are Required + +You need `npx expo run:ios/android` or `eas build` ONLY when using: + +- **Local Expo modules** (custom native code in `modules/`) +- **Apple targets** (widgets, app clips, extensions via `@bacons/apple-targets`) +- **Third-party native modules** not included in Expo Go +- **Custom native configuration** that can't be expressed in `app.json` + +### When Expo Go Works + +Expo Go supports a huge range of features out of the box: + +- All `expo-*` packages (camera, location, notifications, etc.) +- Expo Router navigation +- Most UI libraries (reanimated, gesture handler, etc.) +- Push notifications, deep links, and more + +**If you're unsure, try Expo Go first.** Creating custom builds adds complexity, slower iteration, and requires Xcode/Android Studio setup. + +## Code Style + +- Be cautious of unterminated strings. Ensure nested backticks are escaped; never forget to escape quotes correctly. +- Always use import statements at the top of the file. +- Always use kebab-case for file names, e.g. `comment-card.tsx` +- Always remove old route files when moving or restructuring navigation +- Never use special characters in file names +- Configure tsconfig.json with path aliases, and prefer aliases over relative imports for refactors. + +## Routes + +See `./references/route-structure.md` for detailed route conventions. + +- Routes belong in the `app` directory. +- Never co-locate components, types, or utilities in the app directory. This is an anti-pattern. +- Ensure the app always has a route that matches "/", it may be inside a group route. + +## Library Preferences + +- Never use modules removed from React Native such as Picker, WebView, SafeAreaView, or AsyncStorage +- Never use legacy expo-permissions +- `expo-audio` not `expo-av` +- `expo-video` not `expo-av` +- `expo-image` with `source="sf:name"` for SF Symbols, not `expo-symbols` or `@expo/vector-icons` +- `react-native-safe-area-context` not react-native SafeAreaView +- `process.env.EXPO_OS` not `Platform.OS` +- `React.use` not `React.useContext` +- `expo-image` Image component instead of intrinsic element `img` +- `expo-glass-effect` for liquid glass backdrops + +## Responsiveness + +- Always wrap root component in a scroll view for responsiveness +- Use `` instead of `` for smarter safe area insets +- `contentInsetAdjustmentBehavior="automatic"` should be applied to FlatList and SectionList as well +- Use flexbox instead of Dimensions API +- ALWAYS prefer `useWindowDimensions` over `Dimensions.get()` to measure screen size + +## Behavior + +- Use expo-haptics conditionally on iOS to make more delightful experiences +- Use views with built-in haptics like `` from React Native and `@react-native-community/datetimepicker` +- When a route belongs to a Stack, its first child should almost always be a ScrollView with `contentInsetAdjustmentBehavior="automatic"` set +- When adding a `ScrollView` to the page it should almost always be the first component inside the route component +- Prefer `headerSearchBarOptions` in Stack.Screen options to add a search bar +- Use the `` prop on text containing data that could be copied +- Consider formatting large numbers like 1.4M or 38k +- Never use intrinsic elements like 'img' or 'div' unless in a webview or Expo DOM component + +# Styling + +Follow Apple Human Interface Guidelines. + +## General Styling Rules + +- Prefer flex gap over margin and padding styles +- Prefer padding over margin where possible +- Always account for safe area, either with stack headers, tabs, or ScrollView/FlatList `contentInsetAdjustmentBehavior="automatic"` +- Ensure both top and bottom safe area insets are accounted for +- Inline styles not StyleSheet.create unless reusing styles is faster +- Add entering and exiting animations for state changes +- Use `{ borderCurve: 'continuous' }` for rounded corners unless creating a capsule shape +- ALWAYS use a navigation stack title instead of a custom text element on the page +- When padding a ScrollView, use `contentContainerStyle` padding and gap instead of padding on the ScrollView itself (reduces clipping) +- CSS and Tailwind are not supported - use inline styles + +## Text Styling + +- Add the `selectable` prop to every `` element displaying important data or error messages +- Counters should use `{ fontVariant: 'tabular-nums' }` for alignment + +## Shadows + +Use CSS `boxShadow` style prop. NEVER use legacy React Native shadow or elevation styles. + +```tsx + +``` + +'inset' shadows are supported. + +# Navigation + +## Link + +Use `` from 'expo-router' for navigation between routes. + +```tsx +import { Link } from 'expo-router'; + +// Basic link + + +// Wrapping custom components + + ... + +``` + +Whenever possible, include a `` to follow iOS conventions. Add context menus and previews frequently to enhance navigation. + +## Stack + +- ALWAYS use `_layout.tsx` files to define stacks +- Use Stack from 'expo-router/stack' for native navigation stacks + +### Page Title + +Set the page title in Stack.Screen options: + +```tsx + +``` + +## Context Menus + +Add long press context menus to Link components: + +```tsx +import { Link } from "expo-router"; + + + + + + + + + + + + {}} /> + {}} /> + + +; +``` + +## Link Previews + +Use link previews frequently to enhance navigation: + +```tsx + + + + + + + + +``` + +Link preview can be used with context menus. + +## Modal + +Present a screen as a modal: + +```tsx + +``` + +Prefer this to building a custom modal component. + +## Sheet + +Present a screen as a dynamic form sheet: + +```tsx + +``` + +- Using `contentStyle: { backgroundColor: "transparent" }` makes the background liquid glass on iOS 26+. + +## Common route structure + +A standard app layout with tabs and stacks inside each tab: + +``` +app/ + _layout.tsx — + (index,search)/ + _layout.tsx — + index.tsx — Main list + search.tsx — Search view +``` + +```tsx +// app/_layout.tsx +import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs"; +import { Theme } from "../components/theme"; + +export default function Layout() { + return ( + + + + + + + + + + ); +} +``` + +Create a shared group route so both tabs can push common screens: + +```tsx +// app/(index,search)/_layout.tsx +import { Stack } from "expo-router/stack"; +import { PlatformColor } from "react-native"; + +export default function Layout({ segment }) { + const screen = segment.match(/\((.*)\)/)?.[1]!; + const titles: Record = { index: "Items", search: "Search" }; + + return ( + + + + + ); +} +``` diff --git a/.agents/skills/building-native-ui/references/animations.md b/.agents/skills/building-native-ui/references/animations.md new file mode 100644 index 0000000..a7cb0bd --- /dev/null +++ b/.agents/skills/building-native-ui/references/animations.md @@ -0,0 +1,189 @@ +# Animations + +Use Reanimated v4. Avoid React Native's built-in Animated API. + +## Entering and Exiting Animations + +Use Animated.View with entering and exiting animations. Layout animations can animate state changes. + +```tsx +import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; + +function App() { + return ; +} +``` + +## On-Scroll Animations + +Create high-performance scroll animations using Reanimated's hooks: + +```tsx +import Animated, { + useAnimatedRef, + useScrollViewOffset, + useAnimatedStyle, + interpolate, +} from "react-native-reanimated"; + +function Page() { + const ref = useAnimatedRef(); + const scroll = useScrollViewOffset(ref); + + const style = useAnimatedStyle(() => ({ + opacity: interpolate(scroll.value, [0, 30], [0, 1], "clamp"), + })); + + return ( + + + + ); +} +``` + +## Common Animation Presets + +### Entering Animations + +- `FadeIn`, `FadeInUp`, `FadeInDown`, `FadeInLeft`, `FadeInRight` +- `SlideInUp`, `SlideInDown`, `SlideInLeft`, `SlideInRight` +- `ZoomIn`, `ZoomInUp`, `ZoomInDown` +- `BounceIn`, `BounceInUp`, `BounceInDown` + +### Exiting Animations + +- `FadeOut`, `FadeOutUp`, `FadeOutDown`, `FadeOutLeft`, `FadeOutRight` +- `SlideOutUp`, `SlideOutDown`, `SlideOutLeft`, `SlideOutRight` +- `ZoomOut`, `ZoomOutUp`, `ZoomOutDown` +- `BounceOut`, `BounceOutUp`, `BounceOutDown` + +### Layout Animations + +- `LinearTransition` — Smooth linear interpolation +- `SequencedTransition` — Sequenced property changes +- `FadingTransition` — Fade between states + +## Customizing Animations + +```tsx + +``` + +### Modifiers + +```tsx +// Duration in milliseconds +FadeIn.duration(300); + +// Delay before starting +FadeIn.delay(100); + +// Spring physics +FadeIn.springify(); +FadeIn.springify().damping(15).stiffness(100); + +// Easing curves +FadeIn.easing(Easing.bezier(0.25, 0.1, 0.25, 1)); + +// Chaining +FadeInDown.duration(400).delay(200).springify(); +``` + +## Shared Value Animations + +For imperative control over animations: + +```tsx +import { useSharedValue, withSpring, withTiming } from "react-native-reanimated"; + +const offset = useSharedValue(0); + +// Spring animation +offset.value = withSpring(100); + +// Timing animation +offset.value = withTiming(100, { duration: 300 }); + +// Use in styles +const style = useAnimatedStyle(() => ({ + transform: [{ translateX: offset.value }], +})); +``` + +## Gesture Animations + +Combine with React Native Gesture Handler: + +```tsx +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated"; + +function DraggableBox() { + const translateX = useSharedValue(0); + const translateY = useSharedValue(0); + + const gesture = Gesture.Pan() + .onUpdate((e) => { + translateX.value = e.translationX; + translateY.value = e.translationY; + }) + .onEnd(() => { + translateX.value = withSpring(0); + translateY.value = withSpring(0); + }); + + const style = useAnimatedStyle(() => ({ + transform: [{ translateX: translateX.value }, { translateY: translateY.value }], + })); + + return ( + + + + ); +} +``` + +## Keyboard Animations + +Animate with keyboard height changes: + +```tsx +import Animated, { useAnimatedKeyboard, useAnimatedStyle } from "react-native-reanimated"; + +function KeyboardAwareView() { + const keyboard = useAnimatedKeyboard(); + + const style = useAnimatedStyle(() => ({ + paddingBottom: keyboard.height.value, + })); + + return {/* content */}; +} +``` + +## Staggered List Animations + +Animate list items with delays: + +```tsx +{ + items.map((item, index) => ( + + + + )); +} +``` + +## Best Practices + +- Add entering and exiting animations for state changes +- Use layout animations when items are added/removed from lists +- Use `useAnimatedStyle` for scroll-driven animations +- Prefer `interpolate` with "clamp" for bounded values +- You can't pass PlatformColors to reanimated views or styles; use static colors instead +- Keep animations under 300ms for responsive feel +- Use spring animations for natural movement +- Avoid animating layout properties (width, height) when possible — prefer transforms diff --git a/.agents/skills/building-native-ui/references/controls.md b/.agents/skills/building-native-ui/references/controls.md new file mode 100644 index 0000000..9bbb4fe --- /dev/null +++ b/.agents/skills/building-native-ui/references/controls.md @@ -0,0 +1,245 @@ +# Native Controls + +Native iOS controls provide built-in haptics, accessibility, and platform-appropriate styling. + +## Switch + +Use for binary on/off settings. Has built-in haptics. + +```tsx +import { Switch } from "react-native"; +import { useState } from "react"; + +const [enabled, setEnabled] = useState(false); + +; +``` + +### Customization + +```tsx + +``` + +## Segmented Control + +Use for non-navigational tabs or mode selection. Avoid changing default colors. + +```tsx +import SegmentedControl from "@react-native-segmented-control/segmented-control"; +import { useState } from "react"; + +const [index, setIndex] = useState(0); + + setIndex(nativeEvent.selectedSegmentIndex)} +/>; +``` + +### Rules + +- Maximum 4 options — use a picker for more +- Keep labels short (1-2 words) +- Avoid custom colors — native styling adapts to dark mode + +### With Icons (iOS 14+) + +```tsx + setIndex(nativeEvent.selectedSegmentIndex)} +/> +``` + +## Slider + +Continuous value selection. + +```tsx +import Slider from "@react-native-community/slider"; +import { useState } from "react"; + +const [value, setValue] = useState(0.5); + +; +``` + +### Customization + +```tsx + +``` + +### Discrete Steps + +```tsx + +``` + +## Date/Time Picker + +Compact pickers with popovers. Has built-in haptics. + +```tsx +import DateTimePicker from "@react-native-community/datetimepicker"; +import { useState } from "react"; + +const [date, setDate] = useState(new Date()); + + { + if (selectedDate) setDate(selectedDate); + }} + mode="datetime" +/>; +``` + +### Modes + +- `date` — Date only +- `time` — Time only +- `datetime` — Date and time + +### Display Styles + +```tsx +// Compact inline (default) + + +// Spinner wheel + + +// Full calendar + +``` + +### Time Intervals + +```tsx + +``` + +### Min/Max Dates + +```tsx + +``` + +## Stepper + +Increment/decrement numeric values. + +```tsx +import { Stepper } from "react-native"; +import { useState } from "react"; + +const [count, setCount] = useState(0); + +; +``` + +## TextInput + +Native text input with various keyboard types. + +```tsx +import { TextInput } from "react-native"; + +; +``` + +### Keyboard Types + +```tsx +// Email + + +// Phone + + +// Number + + +// Password + + +// Search + +``` + +### Multiline + +```tsx + +``` + +## Picker (Wheel) + +For selection from many options (5+ items). + +```tsx +import { Picker } from "@react-native-picker/picker"; +import { useState } from "react"; + +const [selected, setSelected] = useState("js"); + + + + + + +; +``` + +## Best Practices + +- **Haptics**: Switch and DateTimePicker have built-in haptics — don't add extra +- **Accessibility**: Native controls have proper accessibility labels by default +- **Dark Mode**: Avoid custom colors — native styling adapts automatically +- **Spacing**: Use consistent padding around controls (12-16pt) +- **Labels**: Place labels above or to the left of controls +- **Grouping**: Group related controls in sections with headers diff --git a/.agents/skills/building-native-ui/references/form-sheet.md b/.agents/skills/building-native-ui/references/form-sheet.md new file mode 100644 index 0000000..c4e4bc1 --- /dev/null +++ b/.agents/skills/building-native-ui/references/form-sheet.md @@ -0,0 +1,251 @@ +# Form Sheets in Expo Router + +This skill covers implementing form sheets with footers using Expo Router's Stack navigator and react-native-screens. + +## Overview + +Form sheets are modal presentations that appear as a card sliding up from the bottom of the screen. They're ideal for: + +- Quick actions and confirmations +- Settings panels +- Login/signup flows +- Action sheets with custom content + +**Requirements:** + +- Expo Router Stack navigator + +## Basic Usage + +### Form Sheet with Footer + +Configure the Stack.Screen with transparent backgrounds and sheet presentation: + +```tsx +// app/_layout.tsx +import { Stack } from "expo-router"; + +export default function Layout() { + return ( + + + + + + + ); +} +``` + +### Form Sheet Screen Content + +> Requires Expo SDK 55 or later. + +Use `flex: 1` to allow the content to fill available space, enabling footer positioning: + +```tsx +// app/about.tsx +import { View, Text, StyleSheet } from "react-native"; + +export default function AboutSheet() { + return ( + + {/* Main content */} + + Sheet Content + + + {/* Footer - stays at bottom */} + + Footer Content + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + content: { + flex: 1, + padding: 16, + }, + footer: { + padding: 16, + }, +}); +``` + +### Formsheet with interactive content below + +Use `sheetLargestUndimmedDetentIndex` (zero-indexed) to keep content behind the form sheet interactive — e.g. letting users pan a map beneath it. Setting it to `1` allows interaction at the first two detents but dims on the third. + +```tsx +// app/_layout.tsx +import { Stack } from "expo-router"; + +export default function Layout() { + return ( + + + + + ); +} +``` + +## Key Options + +| Option | Type | Description | +| --------------------- | ---------- | ----------------------------------------------------------- | +| `presentation` | `string` | Set to `'formSheet'` for sheet presentation | +| `sheetGrabberVisible` | `boolean` | Shows the drag handle at the top of the sheet | +| `sheetAllowedDetents` | `number[]` | Array of detent heights (0-1 range, e.g., `[0.25]` for 25%) | +| `headerTransparent` | `boolean` | Makes header background transparent | +| `contentStyle` | `object` | Style object for the screen content container | +| `title` | `string` | Screen title (set to `''` for no title) | + +## Common Detent Values + +- `[0.25]` - Quarter sheet (compact actions) +- `[0.5]` - Half sheet (medium content) +- `[0.75]` - Three-quarter sheet (detailed forms) +- `[0.25, 0.5, 1]` - Multiple stops (expandable sheet) + +## Complete Example + +```tsx +// _layout.tsx +import { Stack } from "expo-router"; + +export default function Layout() { + return ( + + + + + + + + + ); +} +``` + +```tsx +// app/confirm.tsx +import { View, Text, Pressable, StyleSheet } from "react-native"; +import { router } from "expo-router"; + +export default function ConfirmSheet() { + return ( + + + Confirm Action + Are you sure you want to proceed? + + + + router.back()}> + Cancel + + router.back()}> + Confirm + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + content: { + flex: 1, + padding: 20, + alignItems: "center", + justifyContent: "center", + }, + title: { + fontSize: 18, + fontWeight: "600", + marginBottom: 8, + }, + description: { + fontSize: 14, + color: "#666", + textAlign: "center", + }, + footer: { + flexDirection: "row", + padding: 16, + gap: 12, + }, + cancelButton: { + flex: 1, + padding: 14, + borderRadius: 10, + backgroundColor: "#f0f0f0", + alignItems: "center", + }, + cancelText: { + fontSize: 16, + fontWeight: "500", + }, + confirmButton: { + flex: 1, + padding: 14, + borderRadius: 10, + backgroundColor: "#007AFF", + alignItems: "center", + }, + confirmText: { + fontSize: 16, + fontWeight: "500", + color: "white", + }, +}); +``` + +## Troubleshooting + +### Content not filling sheet + +Make sure the root View uses `flex: 1`: + +```tsx +{/* content */} +``` + +### Sheet background showing through + +Set `contentStyle: { backgroundColor: 'transparent' }` in options and style your content container with the desired background color instead. diff --git a/.agents/skills/building-native-ui/references/gradients.md b/.agents/skills/building-native-ui/references/gradients.md new file mode 100644 index 0000000..63dc14f --- /dev/null +++ b/.agents/skills/building-native-ui/references/gradients.md @@ -0,0 +1,116 @@ +# CSS Gradients + +> **New Architecture Only**: CSS gradients require React Native's New Architecture (Fabric). They are not available in the old architecture or Expo Go. + +Use CSS gradients with the `experimental_backgroundImage` style property. + +## Linear Gradients + +```tsx +// Top to bottom + + +// Left to right + + +// Diagonal + + +// Using degrees + +``` + +## Radial Gradients + +```tsx +// Circle at center + + +// Ellipse + + +// Positioned + +``` + +## Multiple Gradients + +Stack multiple gradients by comma-separating them: + +```tsx + +``` + +## Common Patterns + +### Overlay on Image + +```tsx + + + + +``` + +### Frosted Glass Effect + +```tsx + +``` + +### Button Gradient + +```tsx + + Submit + +``` + +## Important Notes + +- Do NOT use `expo-linear-gradient` — use CSS gradients instead +- Gradients are strings, not objects +- Use `rgba()` for transparency, or `transparent` keyword +- Color stops use percentages (0%, 50%, 100%) +- Direction keywords: `to top`, `to bottom`, `to left`, `to right`, `to top left`, etc. +- Degree values: `45deg`, `90deg`, `135deg`, etc. diff --git a/.agents/skills/building-native-ui/references/icons.md b/.agents/skills/building-native-ui/references/icons.md new file mode 100644 index 0000000..962a462 --- /dev/null +++ b/.agents/skills/building-native-ui/references/icons.md @@ -0,0 +1,218 @@ +# Icons (SF Symbols) + +Use SF Symbols for native feel. Never use FontAwesome or Ionicons. + +## Basic Usage + +```tsx +import { SymbolView } from "expo-symbols"; +import { PlatformColor } from "react-native"; + +; +``` + +## Props + +```tsx + +``` + +## Common Icons + +### Navigation & Actions + +- `house.fill` - home +- `gear` - settings +- `magnifyingglass` - search +- `plus` - add +- `xmark` - close +- `chevron.left` - back +- `chevron.right` - forward +- `arrow.left` - back arrow +- `arrow.right` - forward arrow + +### Media + +- `play.fill` - play +- `pause.fill` - pause +- `stop.fill` - stop +- `backward.fill` - rewind +- `forward.fill` - fast forward +- `speaker.wave.2.fill` - volume +- `speaker.slash.fill` - mute + +### Camera + +- `camera` - camera +- `camera.fill` - camera filled +- `arrow.triangle.2.circlepath` - flip camera +- `photo` - gallery/photos +- `bolt` - flash +- `bolt.slash` - flash off + +### Communication + +- `message` - message +- `message.fill` - message filled +- `envelope` - email +- `envelope.fill` - email filled +- `phone` - phone +- `phone.fill` - phone filled +- `video` - video call +- `video.fill` - video call filled + +### Social + +- `heart` - like +- `heart.fill` - liked +- `star` - favorite +- `star.fill` - favorited +- `hand.thumbsup` - thumbs up +- `hand.thumbsdown` - thumbs down +- `person` - profile +- `person.fill` - profile filled +- `person.2` - people +- `person.2.fill` - people filled + +### Content Actions + +- `square.and.arrow.up` - share +- `square.and.arrow.down` - download +- `doc.on.doc` - copy +- `trash` - delete +- `pencil` - edit +- `folder` - folder +- `folder.fill` - folder filled +- `bookmark` - bookmark +- `bookmark.fill` - bookmarked + +### Status & Feedback + +- `checkmark` - success/done +- `checkmark.circle.fill` - completed +- `xmark.circle.fill` - error/failed +- `exclamationmark.triangle` - warning +- `info.circle` - info +- `questionmark.circle` - help +- `bell` - notification +- `bell.fill` - notification filled + +### Misc + +- `ellipsis` - more options +- `ellipsis.circle` - more in circle +- `line.3.horizontal` - menu/hamburger +- `slider.horizontal.3` - filters +- `arrow.clockwise` - refresh +- `location` - location +- `location.fill` - location filled +- `map` - map +- `mappin` - pin +- `clock` - time +- `calendar` - calendar +- `link` - link +- `nosign` - block/prohibited + +## Animated Symbols + +```tsx + +``` + +### Animation Effects + +- `bounce` - Bouncy animation +- `pulse` - Pulsing effect +- `variableColor` - Color cycling +- `scale` - Scale animation + +```tsx +// Bounce with direction +animationSpec={{ + effect: { type: "bounce", direction: "up" } // up | down +}} + +// Pulse +animationSpec={{ + effect: { type: "pulse" } +}} + +// Variable color (multicolor symbols) +animationSpec={{ + effect: { + type: "variableColor", + cumulative: true, + reversing: true + } +}} +``` + +## Symbol Weights + +```tsx +// Lighter weights + + + + +// Default + + +// Heavier weights + + + + + +``` + +## Symbol Scales + +```tsx + + // default + +``` + +## Multicolor Symbols + +Some symbols support multiple colors: + +```tsx + +``` + +## Finding Symbol Names + +1. Use the SF Symbols app on macOS (free from Apple) +2. Search at https://developer.apple.com/sf-symbols/ +3. Symbol names use dot notation: `square.and.arrow.up` + +## Best Practices + +- Always use SF Symbols over vector icon libraries +- Match symbol weight to nearby text weight +- Use `.fill` variants for selected/active states +- Use PlatformColor for tint to support dark mode +- Keep icons at consistent sizes (16, 20, 24, 32) diff --git a/.agents/skills/building-native-ui/references/media.md b/.agents/skills/building-native-ui/references/media.md new file mode 100644 index 0000000..6731bcd --- /dev/null +++ b/.agents/skills/building-native-ui/references/media.md @@ -0,0 +1,229 @@ +# Media + +## Camera + +- Hide navigation headers when there's a full screen camera +- Ensure to flip the camera with `mirror` to emulate social apps +- Use liquid glass buttons on cameras +- Icons: `arrow.triangle.2.circlepath` (flip), `photo` (gallery), `bolt` (flash) +- Eagerly request camera permission +- Lazily request media library permission + +```tsx +import React, { useRef, useState } from "react"; +import { View, TouchableOpacity, Text, Alert } from "react-native"; +import { CameraView, CameraType, useCameraPermissions } from "expo-camera"; +import * as MediaLibrary from "expo-media-library"; +import * as ImagePicker from "expo-image-picker"; +import * as Haptics from "expo-haptics"; +import { SymbolView } from "expo-symbols"; +import { PlatformColor } from "react-native"; +import { GlassView } from "expo-glass-effect"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +function Camera({ onPicture }: { onPicture: (uri: string) => Promise }) { + const [permission, requestPermission] = useCameraPermissions(); + const cameraRef = useRef(null); + const [type, setType] = useState("back"); + const { bottom } = useSafeAreaInsets(); + + if (!permission?.granted) { + return ( + + + Camera access is required + + + + Grant Permission + + + + ); + } + + const takePhoto = async () => { + await Haptics.selectionAsync(); + if (!cameraRef.current) return; + const photo = await cameraRef.current.takePictureAsync({ quality: 0.8 }); + await onPicture(photo.uri); + }; + + const selectPhoto = async () => { + await Haptics.selectionAsync(); + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: "images", + allowsEditing: false, + quality: 0.8, + }); + if (!result.canceled && result.assets?.[0]) { + await onPicture(result.assets[0].uri); + } + }; + + return ( + + + + + + + + + setType((t) => (t === "back" ? "front" : "back"))} + icon="arrow.triangle.2.circlepath" + /> + + + + ); +} +``` + +## Audio Playback + +Use `expo-audio` not `expo-av`: + +```tsx +import { useAudioPlayer } from "expo-audio"; + +const player = useAudioPlayer({ uri: "https://stream.nightride.fm/rektory.mp3" }); + +; +``` + +### CORRECT (Native patterns) + +```tsx +// DO THIS - Native pattern (Uniwind, React Native components) +import { Button } from "heroui-native"; + +; +``` + +**Always fetch Native docs before implementing.** Do not assume React web patterns work. + +--- + +## Core Principles + +- Semantic variants (`primary`, `secondary`, `tertiary`) over visual descriptions +- Composition over configuration (compound components) +- Theme variables with HSL color format +- React Native StyleSheet patterns with Uniwind utilities + +--- + +## Accessing Documentation & Component Information + +**For component details, examples, props, and implementation patterns, always fetch documentation:** + +### Using Scripts + +```bash +# List all available components +node scripts/list_components.mjs + +# Get component documentation (MDX) +node scripts/get_component_docs.mjs Button +node scripts/get_component_docs.mjs Button Card TextField + +# Get theme variables +node scripts/get_theme.mjs + +# Get non-component docs (guides, releases) +node scripts/get_docs.mjs /docs/native/getting-started/theming +``` + +### Direct MDX URLs + +Component docs: `https://v3.heroui.com/docs/native/components/{component-name}.mdx` + +Examples: + +- Button: `https://v3.heroui.com/docs/native/components/button.mdx` +- Dialog: `https://v3.heroui.com/docs/native/components/dialog.mdx` +- TextField: `https://v3.heroui.com/docs/native/components/text-field.mdx` + +Getting started guides: `https://v3.heroui.com/docs/native/getting-started/{topic}.mdx` + +**Important:** Always fetch component docs before implementing. The MDX docs include complete examples, props, anatomy, and API references. + +--- + +## Installation Essentials + +**CRITICAL**: HeroUI Native is currently in BETA. + +### Quick Install + +```bash +npm i heroui-native +``` + +### Required Peer Dependencies + +```bash +npm i react-native-reanimated react-native-gesture-handler react-native-safe-area-context @gorhom/bottom-sheet react-native-svg react-native-worklets tailwind-merge tailwind-variants +``` + +### Framework Setup (Expo - Recommended) + +1. **Install dependencies:** + +```bash +npx create-expo-app MyApp +cd MyApp +npm i heroui-native uniwind tailwindcss +npm i react-native-reanimated react-native-gesture-handler react-native-safe-area-context @gorhom/bottom-sheet react-native-svg react-native-worklets tailwind-merge tailwind-variants +``` + +2. **Create `global.css`:** + +```css +@import "tailwindcss"; +@import "uniwind"; +@import "heroui-native/styles"; + +@source "./node_modules/heroui-native/lib"; +``` + +3. **Wrap app with providers:** + +```tsx +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { HeroUINativeProvider } from "heroui-native"; +import "./global.css"; + +export default function Layout() { + return ( + + + + + + ); +} +``` + +### Critical Setup Requirements + +1. **Uniwind is Required** - HeroUI Native uses Uniwind (Tailwind CSS for React Native) +2. **HeroUINativeProvider Required** - Wrap your app with `HeroUINativeProvider` +3. **GestureHandlerRootView Required** - Wrap with `GestureHandlerRootView` from react-native-gesture-handler +4. **Use Compound Components** - Components use compound structure (e.g., `Card.Header`, `Card.Body`) +5. **Use onPress, not onClick** - React Native uses `onPress` event handlers +6. **Platform-Specific Code** - Use `Platform.OS` for iOS/Android differences + +--- + +## Component Patterns + +HeroUI Native uses **compound component patterns**. Each component has subcomponents accessed via dot notation. + +**Example - Card:** + +```tsx + + + Title + Description + + {/* Content */} + {/* Actions */} + +``` + +**Key Points:** + +- Always use compound structure - don't flatten to props +- Subcomponents are accessed via dot notation (e.g., `Card.Header`) +- Each subcomponent may have its own props +- **Fetch component docs for complete anatomy and examples** + +--- + +## Semantic Variants + +HeroUI uses semantic naming to communicate functional intent: + +| Variant | Purpose | Usage | +| ------------- | --------------------------------- | -------------- | +| `primary` | Main action to move forward | 1 per context | +| `secondary` | Alternative actions | Multiple | +| `tertiary` | Dismissive actions (cancel, skip) | Sparingly | +| `danger` | Destructive actions | When needed | +| `danger-soft` | Soft destructive actions | Less prominent | +| `ghost` | Low-emphasis actions | Minimal weight | +| `outline` | Secondary actions | Bordered style | + +**Don't use raw colors** - semantic variants adapt to themes and accessibility. + +--- + +## Theming + +HeroUI Native uses CSS variables via Tailwind/Uniwind for theming. Theme colors are defined in `global.css`: + +```css +@theme { + --color-accent: hsl(260, 100%, 70%); + --color-accent-foreground: hsl(0, 0%, 100%); +} +``` + +**Get current theme variables:** + +```bash +node scripts/get_theme.mjs +``` + +**Access theme colors programmatically:** + +```tsx +import { useThemeColor } from "heroui-native"; + +const accentColor = useThemeColor("accent"); +``` + +**Theme switching (Light/Dark Mode):** + +```tsx +import { Uniwind, useUniwind } from "uniwind"; + +const { theme } = useUniwind(); +Uniwind.setTheme(theme === "light" ? "dark" : "light"); +``` + +For detailed theming, fetch: `https://v3.heroui.com/docs/native/getting-started/theming.mdx` diff --git a/.agents/skills/heroui-native/scripts/get_component_docs.mjs b/.agents/skills/heroui-native/scripts/get_component_docs.mjs new file mode 100755 index 0000000..d0d0657 --- /dev/null +++ b/.agents/skills/heroui-native/scripts/get_component_docs.mjs @@ -0,0 +1,157 @@ +#!/usr/bin/env node +/** + * Get complete component documentation (MDX) for HeroUI Native components. + * + * Usage: + * node get_component_docs.mjs Button + * node get_component_docs.mjs Button Card TextField + * + * Output: + * MDX documentation including imports, usage, variants, props, examples + */ + +const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com"; +const FALLBACK_BASE = "https://v3.heroui.com"; +const APP_PARAM = "app=native-skills"; + +/** + * Convert PascalCase to kebab-case. + */ +function toKebabCase(name) { + return name + .replace(/([a-z])([A-Z])/g, "$1-$2") + .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") + .toLowerCase(); +} + +/** + * Fetch data from HeroUI Native API with app parameter for analytics. + */ +async function fetchApi(endpoint, method = "GET", body = null) { + const separator = endpoint.includes("?") ? "&" : "?"; + const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`; + + try { + const options = { + headers: { + "Content-Type": "application/json", + "User-Agent": "HeroUI-Native-Skill/1.0", + }, + method, + signal: AbortSignal.timeout(30000), + }; + + if (body) { + options.body = JSON.stringify(body); + } + + const response = await fetch(url, options); + + if (!response.ok) { + return null; + } + + return await response.json(); + } catch { + return null; + } +} + +/** + * Fetch MDX directly from v3.heroui.com as fallback. + */ +async function fetchFallback(component) { + const kebabName = toKebabCase(component); + const url = `${FALLBACK_BASE}/docs/native/components/${kebabName}.mdx`; + + try { + const response = await fetch(url, { + headers: { "User-Agent": "HeroUI-Native-Skill/1.0" }, + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + return { component, error: `Failed to fetch docs for ${component}` }; + } + + const content = await response.text(); + + return { + component, + content, + contentType: "mdx", + source: "fallback", + url, + }; + } catch { + return { component, error: `Failed to fetch docs for ${component}` }; + } +} + +/** + * Main function to get component documentation. + */ +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error("Usage: node get_component_docs.mjs [Component2] ..."); + console.error("Example: node get_component_docs.mjs Button Card"); + process.exit(1); + } + + const components = args; + + // Try API first - use POST /v1/components/docs for batch requests + console.error(`# Fetching Native docs for: ${components.join(", ")}...`); + const data = await fetchApi("/v1/components/docs", "POST", { components }); + + if (data && data.results) { + // Output results + if (data.results.length === 1) { + // Single component - output content directly for easier reading + const result = data.results[0]; + + if (result.content) { + console.log(result.content); + } else if (result.error) { + console.error(`# Error for ${result.component}: ${result.error}`); + console.log(JSON.stringify(result, null, 2)); + } else { + console.log(JSON.stringify(result, null, 2)); + } + } else { + // Multiple components - output as JSON array + console.log(JSON.stringify(data, null, 2)); + } + + return; + } + + // Fallback to individual component fetches + console.error("# API failed, using fallback..."); + const results = []; + + for (const component of components) { + const result = await fetchFallback(component); + + results.push(result); + } + + // Output results + if (results.length === 1) { + // Single component - output content directly for easier reading + const result = results[0]; + + if (result.content) { + console.log(result.content); + } else { + console.log(JSON.stringify(result, null, 2)); + } + } else { + // Multiple components - output as JSON array + console.log(JSON.stringify(results, null, 2)); + } +} + +main(); diff --git a/.agents/skills/heroui-native/scripts/get_docs.mjs b/.agents/skills/heroui-native/scripts/get_docs.mjs new file mode 100755 index 0000000..aeef412 --- /dev/null +++ b/.agents/skills/heroui-native/scripts/get_docs.mjs @@ -0,0 +1,154 @@ +#!/usr/bin/env node +/** + * Get non-component HeroUI Native documentation (guides, theming, releases). + * + * Usage: + * node get_docs.mjs /docs/native/getting-started/theming + * node get_docs.mjs /docs/native/releases/beta-12 + * + * Output: + * MDX documentation content + * + * Note: For component docs, use get_component_docs.mjs instead. + */ + +const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com"; +const FALLBACK_BASE = "https://v3.heroui.com"; +const APP_PARAM = "app=native-skills"; + +/** + * Fetch documentation from HeroUI Native API. + * Uses v1 endpoint pattern: /v1/docs/:path + */ +async function fetchApi(path) { + // The v1 API expects path without /docs/ prefix + // Input: /docs/native/getting-started/theming + // API expects: native/getting-started/theming (route is /v1/docs/:path(*)) + let apiPath = path.startsWith("/docs/") + ? path.slice(6) // Remove /docs/ prefix + : path.startsWith("/") + ? path.slice(1) // Remove leading / + : path; + + const separator = "?"; + const url = `${API_BASE}/v1/docs/${apiPath}${separator}${APP_PARAM}`; + + try { + const response = await fetch(url, { + headers: { "User-Agent": "HeroUI-Native-Skill/1.0" }, + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + console.error(`# API Error: HTTP ${response.status}`); + + return null; + } + + return await response.json(); + } catch (error) { + console.error(`# API Error: ${error.message}`); + + return null; + } +} + +/** + * Fetch MDX directly from v3.heroui.com as fallback. + */ +async function fetchFallback(path) { + // Ensure path starts with /docs and ends with .mdx + let cleanPath = path.replace(/^\//, ""); + + if (!cleanPath.endsWith(".mdx")) { + cleanPath = `${cleanPath}.mdx`; + } + + const url = `${FALLBACK_BASE}/${cleanPath}`; + + try { + const response = await fetch(url, { + headers: { "User-Agent": "HeroUI-Native-Skill/1.0" }, + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + return { error: `HTTP ${response.status}: ${response.statusText}`, path }; + } + + const content = await response.text(); + + return { + content, + contentType: "mdx", + path, + source: "fallback", + url, + }; + } catch (error) { + return { error: `Fetch Error: ${error.message}`, path }; + } +} + +/** + * Main function to get documentation for specified path. + */ +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error("Usage: node get_docs.mjs "); + console.error("Example: node get_docs.mjs /docs/native/getting-started/theming"); + console.error(); + console.error("Available paths include:"); + console.error(" /docs/native/getting-started/theming"); + console.error(" /docs/native/getting-started/colors"); + console.error(" /docs/native/getting-started/styling"); + console.error(" /docs/native/releases/beta-12"); + console.error(); + console.error("Note: For component docs, use get_component_docs.mjs instead."); + process.exit(1); + } + + const path = args[0]; + + // Check if user is trying to get component docs + if (path.includes("/components/")) { + console.error("# Warning: Use get_component_docs.mjs for component documentation."); + const componentName = path.split("/").pop().replace(".mdx", ""); + const titleCase = componentName.charAt(0).toUpperCase() + componentName.slice(1); + + console.error(`# Example: node get_component_docs.mjs ${titleCase}`); + } + + // Validate Native path + if (!path.startsWith("/docs/native/")) { + console.error("# Warning: Native documentation paths should start with /docs/native/"); + console.error(`# Provided path: ${path}`); + } + + console.error(`# Fetching Native documentation for ${path}...`); + + // Try API first + const data = await fetchApi(path); + + if (data && data.content) { + data.source = "api"; + console.log(data.content); + + return; + } + + // Fallback to direct fetch + console.error("# API failed, using fallback..."); + const fallbackData = await fetchFallback(path); + + if (fallbackData.content) { + console.log(fallbackData.content); + } else { + console.log(JSON.stringify(fallbackData, null, 2)); + process.exit(1); + } +} + +main(); diff --git a/.agents/skills/heroui-native/scripts/get_theme.mjs b/.agents/skills/heroui-native/scripts/get_theme.mjs new file mode 100755 index 0000000..86013cb --- /dev/null +++ b/.agents/skills/heroui-native/scripts/get_theme.mjs @@ -0,0 +1,222 @@ +#!/usr/bin/env node +/** + * Get theme variables and design tokens for HeroUI Native. + * + * Usage: + * node get_theme.mjs + * + * Output: + * Theme variables organized by light/dark with HSL color format + */ + +const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com"; +const APP_PARAM = "app=native-skills"; + +// Fallback theme reference when API is unavailable +const FALLBACK_THEME = { + borderRadius: { + full: 9999, + lg: 12, + md: 8, + sm: 6, + }, + dark: { + colors: [ + { + category: "base", + name: "--color-background", + value: "hsl(0, 0%, 14.5%)", + }, + { + category: "semantic", + name: "--color-foreground", + value: "hsl(0, 0%, 98.4%)", + }, + { + category: "semantic", + name: "--color-accent", + value: "hsl(264.1, 100%, 55.1%)", + }, + { + category: "status", + name: "--color-danger", + value: "hsl(25.3, 100%, 63.7%)", + }, + { + category: "status", + name: "--color-success", + value: "hsl(163.2, 100%, 76.5%)", + }, + { + category: "status", + name: "--color-warning", + value: "hsl(86.0, 100%, 79.5%)", + }, + ], + }, + latestVersion: "beta", + light: { + colors: [ + { + category: "base", + name: "--color-background", + value: "hsl(0, 0%, 100%)", + }, + { + category: "semantic", + name: "--color-foreground", + value: "hsl(285.89, 5.9%, 21.03%)", + }, + { + category: "semantic", + name: "--color-accent", + value: "hsl(253.83, 100%, 62.04%)", + }, + { + category: "status", + name: "--color-danger", + value: "hsl(25.74, 100%, 65.32%)", + }, + { + category: "status", + name: "--color-success", + value: "hsl(150.81, 100%, 73.29%)", + }, + { + category: "status", + name: "--color-warning", + value: "hsl(72.33, 100%, 78.19%)", + }, + ], + }, + note: "This is a fallback. For complete theme variables, ensure the API is accessible.", + opacity: { + disabled: 0.4, + hover: 0.8, + pressed: 0.6, + }, + source: "fallback", + theme: "default", +}; + +/** + * Fetch data from HeroUI Native API with app parameter for analytics. + */ +async function fetchApi(endpoint) { + const separator = endpoint.includes("?") ? "&" : "?"; + const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`; + + try { + const response = await fetch(url, { + headers: { "User-Agent": "HeroUI-Native-Skill/1.0" }, + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + console.error(`# API Error: HTTP ${response.status}`); + + return null; + } + + return await response.json(); + } catch (error) { + console.error(`# API Error: ${error.message}`); + + return null; + } +} + +/** + * Format colors grouped by category. + */ +function formatColors(colors) { + const grouped = {}; + + for (const color of colors) { + const category = color.category || "semantic"; + + if (!grouped[category]) { + grouped[category] = []; + } + grouped[category].push(color); + } + + const lines = []; + + for (const [category, tokens] of Object.entries(grouped)) { + lines.push(` /* ${category.charAt(0).toUpperCase() + category.slice(1)} Colors */`); + for (const token of tokens) { + const name = token.name || ""; + const value = token.value || ""; + + lines.push(` ${name}: ${value};`); + } + lines.push(""); + } + + return lines.join("\n"); +} + +/** + * Main function to get theme variables. + */ +async function main() { + console.error("# Fetching Native theme variables..."); + + const rawData = await fetchApi("/v1/themes/variables?theme=default"); + + let data; + let version; + + if (!rawData) { + console.error("# API failed, using fallback theme reference..."); + data = FALLBACK_THEME; + version = FALLBACK_THEME.latestVersion || "unknown"; + } else { + // Handle API response format + data = rawData; + version = rawData.latestVersion || "unknown"; + } + + // Output as formatted structure for readability + console.log("/* HeroUI Native Theme Variables */"); + console.log(`/* Theme: ${data.theme || "default"} */`); + console.log(`/* Version: ${version} */`); + console.log(); + + // Light mode colors + if (data.light && data.light.colors) { + console.log("/* Light Mode Colors */"); + console.log(formatColors(data.light.colors)); + } + + // Dark mode colors + if (data.dark && data.dark.colors) { + console.log("/* Dark Mode Colors */"); + console.log(formatColors(data.dark.colors)); + } + + // Border radius + if (data.borderRadius) { + console.log("/* Border Radius */"); + for (const [key, value] of Object.entries(data.borderRadius)) { + console.log(` --radius-${key}: ${value};`); + } + console.log(); + } + + // Opacity + if (data.opacity) { + console.log("/* Opacity */"); + for (const [key, value] of Object.entries(data.opacity)) { + console.log(` --opacity-${key}: ${value};`); + } + console.log(); + } + + // Also output raw JSON to stderr for programmatic use + console.error("\n# Raw JSON output:"); + console.error(JSON.stringify(rawData || data, null, 2)); +} + +main(); diff --git a/.agents/skills/heroui-native/scripts/list_components.mjs b/.agents/skills/heroui-native/scripts/list_components.mjs new file mode 100755 index 0000000..c59b437 --- /dev/null +++ b/.agents/skills/heroui-native/scripts/list_components.mjs @@ -0,0 +1,134 @@ +#!/usr/bin/env node +/** + * List all available HeroUI Native components. + * + * Usage: + * node list_components.mjs + * + * Output: + * JSON with components array, latestVersion, and count + */ + +const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com"; +const APP_PARAM = "app=native-skills"; +const LLMS_TXT_URL = "https://v3.heroui.com/native/llms.txt"; + +/** + * Fetch data from HeroUI Native API with app parameter for analytics. + */ +async function fetchApi(endpoint) { + const separator = endpoint.includes("?") ? "&" : "?"; + const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`; + + try { + const response = await fetch(url, { + headers: { "User-Agent": "HeroUI-Native-Skill/1.0" }, + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + console.error(`HTTP Error ${response.status}: ${response.statusText}`); + + return null; + } + + return await response.json(); + } catch (error) { + console.error(`API Error: ${error.message}`); + + return null; + } +} + +/** + * Fetch component list from llms.txt fallback URL. + */ +async function fetchFallback() { + try { + const response = await fetch(LLMS_TXT_URL, { + headers: { "User-Agent": "HeroUI-Native-Skill/1.0" }, + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + return null; + } + + const content = await response.text(); + + // Parse markdown to extract component names from pattern: - [ComponentName](url) + // Look for links under the Components section (### Components) + const components = []; + let inComponentsSection = false; + + for (const line of content.split("\n")) { + // Check if we're entering the Components section (uses ### header) + if (line.trim() === "### Components") { + inComponentsSection = true; + continue; + } + + // Check if we're leaving the Components section (another ### header) + if (inComponentsSection && line.trim().startsWith("### ")) { + break; + } + + // Extract component name from markdown link pattern + // Match: - [ComponentName](https://v3.heroui.com/docs/native/components/component-name) + // Skip "All Components" which links to /components without a specific component + if (inComponentsSection) { + const match = line.match( + /^\s*-\s*\[([^\]]+)\]\(https:\/\/v3\.heroui\.com\/docs\/native\/components\/[a-z]/, + ); + + if (match) { + components.push(match[1]); + } + } + } + + if (components.length > 0) { + console.error(`# Using fallback: ${LLMS_TXT_URL}`); + + return { + components: components.sort(), + count: components.length, + latestVersion: "unknown", + }; + } + + return null; + } catch (error) { + console.error(`Fallback Error: ${error.message}`); + + return null; + } +} + +/** + * Main function to list all available HeroUI Native components. + */ +async function main() { + let data = await fetchApi("/v1/components"); + + // Check if API returned valid data with components + if (!data || !data.components || data.components.length === 0) { + console.error("# API returned no components, trying fallback..."); + data = await fetchFallback(); + } + + if (!data || !data.components || data.components.length === 0) { + console.error("Error: Failed to fetch component list from API and fallback"); + process.exit(1); + } + + // Output formatted JSON + console.log(JSON.stringify(data, null, 2)); + + // Print summary to stderr for human readability + console.error( + `\n# Found ${data.components.length} Native components (${data.latestVersion || "unknown"})`, + ); +} + +main(); diff --git a/.agents/skills/hono/SKILL.md b/.agents/skills/hono/SKILL.md new file mode 100644 index 0000000..8b00a0e --- /dev/null +++ b/.agents/skills/hono/SKILL.md @@ -0,0 +1,90 @@ +--- +name: hono +description: Efficiently develop Hono applications using Hono CLI. Supports documentation search, API reference lookup, request testing, and bundle optimization. +--- + +# Hono Skill + +Develop Hono applications efficiently using Hono CLI (`@hono/cli`). + +## Setup + +You can use Hono CLI without global installation via npx: + +```bash +npx @hono/cli +``` + +Or install globally (optional): + +```bash +npm install -g @hono/cli +``` + +## Commands for AI + +### 1. Search Documentation + +```bash +hono search "" --pretty +``` + +Search for Hono APIs and features. Use `--pretty` for human-readable output. + +### 2. View Documentation + +```bash +hono docs [path] +``` + +Display detailed documentation for a specific path found in search results. + +**Examples:** + +```bash +hono docs /docs/api/context +hono docs /docs/api/hono +hono docs /docs/helpers/factory +``` + +### 3. Request Testing + +```bash +# GET request +hono request [file] -P /path + +# POST request +hono request [file] -X POST -P /api/users -d '{"name": "test"}' + +# Request with headers +hono request [file] -H "Authorization: Bearer token" -P /api/protected +``` + +Uses `app.request()` internally, so no server startup required for testing. + +### 4. Optimization & Bundling + +```bash +# Bundle optimization +hono optimize [entry] -o dist/index.js + +# With minification +hono optimize [entry] -o dist/index.js --minify + +# Specify target (cloudflare-workers, deno, etc.) +hono optimize [entry] -t cloudflare-workers +``` + +## Development Workflow + +1. **Research**: Use `hono search` → `hono docs` to investigate APIs and features +2. **Implement**: Write the code +3. **Test**: Use `hono request` to test endpoints +4. **Optimize**: Use `hono optimize` for production builds when needed + +## Guidelines + +- Always search with `hono search` before implementing unfamiliar APIs +- Use `--pretty` flag with `hono search` (default output is JSON) +- `hono request` works without starting an HTTP server +- Search for middleware usage with `hono search "middleware name"` diff --git a/.agents/skills/native-data-fetching/SKILL.md b/.agents/skills/native-data-fetching/SKILL.md new file mode 100644 index 0000000..49afccc --- /dev/null +++ b/.agents/skills/native-data-fetching/SKILL.md @@ -0,0 +1,503 @@ +--- +name: native-data-fetching +description: Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, and Expo Router data loaders (useLoaderData). +version: 1.0.0 +license: MIT +--- + +# Expo Networking + +**You MUST use this skill for ANY networking work including API requests, data fetching, caching, or network debugging.** + +## References + +Consult these resources as needed: + +``` +references/ + expo-router-loaders.md Route-level data loading with Expo Router loaders (web, SDK 55+) +``` + +## When to Use + +Use this skill when: + +- Implementing API requests +- Setting up data fetching (React Query, SWR) +- Using Expo Router data loaders (`useLoaderData`, web SDK 55+) +- Debugging network failures +- Implementing caching strategies +- Handling offline scenarios +- Authentication/token management +- Configuring API URLs and environment variables + +## Preferences + +- Avoid axios, prefer expo/fetch + +## Common Issues & Solutions + +### 1. Basic Fetch Usage + +**Simple GET request**: + +```tsx +const fetchUser = async (userId: string) => { + const response = await fetch(`https://api.example.com/users/${userId}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response.json(); +}; +``` + +**POST request with body**: + +```tsx +const createUser = async (userData: UserData) => { + const response = await fetch("https://api.example.com/users", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(userData), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message); + } + + return response.json(); +}; +``` + +--- + +### 2. React Query (TanStack Query) + +**Setup**: + +```tsx +// app/_layout.tsx +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5 minutes + retry: 2, + }, + }, +}); + +export default function RootLayout() { + return ( + + + + ); +} +``` + +**Fetching data**: + +```tsx +import { useQuery } from "@tanstack/react-query"; + +function UserProfile({ userId }: { userId: string }) { + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ["user", userId], + queryFn: () => fetchUser(userId), + }); + + if (isLoading) return ; + if (error) return ; + + return ; +} +``` + +**Mutations**: + +```tsx +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +function CreateUserForm() { + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: createUser, + onSuccess: () => { + // Invalidate and refetch + queryClient.invalidateQueries({ queryKey: ["users"] }); + }, + }); + + const handleSubmit = (data: UserData) => { + mutation.mutate(data); + }; + + return
; +} +``` + +--- + +### 3. Error Handling + +**Comprehensive error handling**: + +```tsx +class ApiError extends Error { + constructor( + message: string, + public status: number, + public code?: string, + ) { + super(message); + this.name = "ApiError"; + } +} + +const fetchWithErrorHandling = async (url: string, options?: RequestInit) => { + try { + const response = await fetch(url, options); + + if (!response.ok) { + const error = await response.json().catch(() => ({})); + throw new ApiError(error.message || "Request failed", response.status, error.code); + } + + return response.json(); + } catch (error) { + if (error instanceof ApiError) { + throw error; + } + // Network error (no internet, timeout, etc.) + throw new ApiError("Network error", 0, "NETWORK_ERROR"); + } +}; +``` + +**Retry logic**: + +```tsx +const fetchWithRetry = async (url: string, options?: RequestInit, retries = 3) => { + for (let i = 0; i < retries; i++) { + try { + return await fetchWithErrorHandling(url, options); + } catch (error) { + if (i === retries - 1) throw error; + // Exponential backoff + await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000)); + } + } +}; +``` + +--- + +### 4. Authentication + +**Token management**: + +```tsx +import * as SecureStore from "expo-secure-store"; + +const TOKEN_KEY = "auth_token"; + +export const auth = { + getToken: () => SecureStore.getItemAsync(TOKEN_KEY), + setToken: (token: string) => SecureStore.setItemAsync(TOKEN_KEY, token), + removeToken: () => SecureStore.deleteItemAsync(TOKEN_KEY), +}; + +// Authenticated fetch wrapper +const authFetch = async (url: string, options: RequestInit = {}) => { + const token = await auth.getToken(); + + return fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: token ? `Bearer ${token}` : "", + }, + }); +}; +``` + +**Token refresh**: + +```tsx +let isRefreshing = false; +let refreshPromise: Promise | null = null; + +const getValidToken = async (): Promise => { + const token = await auth.getToken(); + + if (!token || isTokenExpired(token)) { + if (!isRefreshing) { + isRefreshing = true; + refreshPromise = refreshToken().finally(() => { + isRefreshing = false; + refreshPromise = null; + }); + } + return refreshPromise!; + } + + return token; +}; +``` + +--- + +### 5. Offline Support + +**Check network status**: + +```tsx +import NetInfo from "@react-native-community/netinfo"; + +// Hook for network status +function useNetworkStatus() { + const [isOnline, setIsOnline] = useState(true); + + useEffect(() => { + return NetInfo.addEventListener((state) => { + setIsOnline(state.isConnected ?? true); + }); + }, []); + + return isOnline; +} +``` + +**Offline-first with React Query**: + +```tsx +import { onlineManager } from "@tanstack/react-query"; +import NetInfo from "@react-native-community/netinfo"; + +// Sync React Query with network status +onlineManager.setEventListener((setOnline) => { + return NetInfo.addEventListener((state) => { + setOnline(state.isConnected ?? true); + }); +}); + +// Queries will pause when offline and resume when online +``` + +--- + +### 6. Environment Variables + +**Using environment variables for API configuration**: + +Expo supports environment variables with the `EXPO_PUBLIC_` prefix. These are inlined at build time and available in your JavaScript code. + +```tsx +// .env +EXPO_PUBLIC_API_URL=https://api.example.com +EXPO_PUBLIC_API_VERSION=v1 + +// Usage in code +const API_URL = process.env.EXPO_PUBLIC_API_URL; + +const fetchUsers = async () => { + const response = await fetch(`${API_URL}/users`); + return response.json(); +}; +``` + +**Environment-specific configuration**: + +```tsx +// .env.development +EXPO_PUBLIC_API_URL=http://localhost:3000 + +// .env.production +EXPO_PUBLIC_API_URL=https://api.production.com +``` + +**Creating an API client with environment config**: + +```tsx +// api/client.ts +const BASE_URL = process.env.EXPO_PUBLIC_API_URL; + +if (!BASE_URL) { + throw new Error("EXPO_PUBLIC_API_URL is not defined"); +} + +export const apiClient = { + get: async (path: string): Promise => { + const response = await fetch(`${BASE_URL}${path}`); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return response.json(); + }, + + post: async (path: string, body: unknown): Promise => { + const response = await fetch(`${BASE_URL}${path}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return response.json(); + }, +}; +``` + +**Important notes**: + +- Only variables prefixed with `EXPO_PUBLIC_` are exposed to the client bundle +- Never put secrets (API keys with write access, database passwords) in `EXPO_PUBLIC_` variables—they're visible in the built app +- Environment variables are inlined at **build time**, not runtime +- Restart the dev server after changing `.env` files +- For server-side secrets in API routes, use variables without the `EXPO_PUBLIC_` prefix + +**TypeScript support**: + +```tsx +// types/env.d.ts +declare global { + namespace NodeJS { + interface ProcessEnv { + EXPO_PUBLIC_API_URL: string; + EXPO_PUBLIC_API_VERSION?: string; + } + } +} + +export {}; +``` + +--- + +### 7. Request Cancellation + +**Cancel on unmount**: + +```tsx +useEffect(() => { + const controller = new AbortController(); + + fetch(url, { signal: controller.signal }) + .then((response) => response.json()) + .then(setData) + .catch((error) => { + if (error.name !== "AbortError") { + setError(error); + } + }); + + return () => controller.abort(); +}, [url]); +``` + +**With React Query** (automatic): + +```tsx +// React Query automatically cancels requests when queries are invalidated +// or components unmount +``` + +--- + +## Decision Tree + +``` +User asks about networking + |-- Route-level data loading (web, SDK 55+)? + | \-- Expo Router loaders — see references/expo-router-loaders.md + | + |-- Basic fetch? + | \-- Use fetch API with error handling + | + |-- Need caching/state management? + | |-- Complex app -> React Query (TanStack Query) + | \-- Simpler needs -> SWR or custom hooks + | + |-- Authentication? + | |-- Token storage -> expo-secure-store + | \-- Token refresh -> Implement refresh flow + | + |-- Error handling? + | |-- Network errors -> Check connectivity first + | |-- HTTP errors -> Parse response, throw typed errors + | \-- Retries -> Exponential backoff + | + |-- Offline support? + | |-- Check status -> NetInfo + | \-- Queue requests -> React Query persistence + | + |-- Environment/API config? + | |-- Client-side URLs -> EXPO_PUBLIC_ prefix in .env + | |-- Server secrets -> Non-prefixed env vars (API routes only) + | \-- Multiple environments -> .env.development, .env.production + | + \-- Performance? + |-- Caching -> React Query with staleTime + |-- Deduplication -> React Query handles this + \-- Cancellation -> AbortController or React Query +``` + +## Common Mistakes + +**Wrong: No error handling** + +```tsx +const data = await fetch(url).then((r) => r.json()); +``` + +**Right: Check response status** + +```tsx +const response = await fetch(url); +if (!response.ok) throw new Error(`HTTP ${response.status}`); +const data = await response.json(); +``` + +**Wrong: Storing tokens in AsyncStorage** + +```tsx +await AsyncStorage.setItem("token", token); // Not secure! +``` + +**Right: Use SecureStore for sensitive data** + +```tsx +await SecureStore.setItemAsync("token", token); +``` + +## Example Invocations + +User: "How do I make API calls in React Native?" +-> Use fetch, wrap with error handling + +User: "Should I use React Query or SWR?" +-> React Query for complex apps, SWR for simpler needs + +User: "My app needs to work offline" +-> Use NetInfo for status, React Query persistence for caching + +User: "How do I handle authentication tokens?" +-> Store in expo-secure-store, implement refresh flow + +User: "API calls are slow" +-> Check caching strategy, use React Query staleTime + +User: "How do I configure different API URLs for dev and prod?" +-> Use EXPO*PUBLIC* env vars with .env.development and .env.production files + +User: "Where should I put my API key?" +-> Client-safe keys: EXPO*PUBLIC* in .env. Secret keys: non-prefixed env vars in API routes only + +User: "How do I load data for a page in Expo Router?" +-> See references/expo-router-loaders.md for route-level loaders (web, SDK 55+). For native, use React Query or fetch. diff --git a/.agents/skills/native-data-fetching/references/expo-router-loaders.md b/.agents/skills/native-data-fetching/references/expo-router-loaders.md new file mode 100644 index 0000000..502fcf1 --- /dev/null +++ b/.agents/skills/native-data-fetching/references/expo-router-loaders.md @@ -0,0 +1,344 @@ +# Expo Router Data Loaders + +Route-level data loading for web apps using Expo SDK 55+. Loaders are async functions exported from route files that load data before the route renders, following the Remix/React Router loader model. + +**Dual execution model:** + +- **Initial page load (SSR):** The loader runs server-side. Its return value is serialized as JSON and embedded in the HTML response. +- **Client-side navigation:** The browser fetches the loader data from the server via HTTP. The route renders once the data arrives. + +You write one function and the framework manages when and how it executes. + +## Configuration + +**Requirements:** Expo SDK 55+, web output mode (`npx expo serve` or `npx expo export --platform web`) set in `app.json` or `app.config.js`. + +**Server rendering:** + +```json +{ + "expo": { + "web": { + "output": "server" + }, + "plugins": [ + [ + "expo-router", + { + "unstable_useServerDataLoaders": true, + "unstable_useServerRendering": true + } + ] + ] + } +} +``` + +**Static/SSG:** + +```json +{ + "expo": { + "web": { + "output": "static" + }, + "plugins": [ + [ + "expo-router", + { + "unstable_useServerDataLoaders": true + } + ] + ] + } +} +``` + +| | `"server"` | `"static"` | +| ------------------------------- | ------------------------------ | ------------------------------------- | +| `unstable_useServerDataLoaders` | Required | Required | +| `unstable_useServerRendering` | Required | Not required | +| Loader runs on | Live server (every request) | Build time (static generation) | +| `request` object | Full access (headers, cookies) | Not available | +| Hosting | Node.js server (EAS Hosting) | Any static host (Netlify, Vercel, S3) | + +## Imports + +Loaders use two packages: + +- **`expo-router`** — `useLoaderData` hook +- **`expo-server`** — `LoaderFunction` type, `StatusError`, `setResponseHeaders`. Always available (dependency of `expo-router`), no install needed. + +## Basic Loader + +For loaders without params, a plain async function works: + +```tsx +// app/posts/index.tsx +import { Suspense } from "react"; +import { useLoaderData } from "expo-router"; +import { ActivityIndicator, View, Text } from "react-native"; + +export async function loader() { + const response = await fetch("https://api.example.com/posts"); + const posts = await response.json(); + return { posts }; +} + +function PostList() { + const { posts } = useLoaderData(); + + return ( + + {posts.map((post) => ( + {post.title} + ))} + + ); +} + +export default function Posts() { + return ( + }> + + + ); +} +``` + +`useLoaderData` is typed via `typeof loader` — the generic parameter infers the return type. + +## Dynamic Routes + +For loaders with params, use the `LoaderFunction` type from `expo-server`. The first argument is the request (an immutable `Request`-like object, or `undefined` in static mode). The second is `params` (`Record`), which contains **path parameters only**. Access individual params with a cast like `params.id as string`. For query parameters, use `new URL(request.url).searchParams`: + +```tsx +// app/posts/[id].tsx +import { Suspense } from "react"; +import { useLoaderData } from "expo-router"; +import { StatusError, type LoaderFunction } from "expo-server"; +import { ActivityIndicator, View, Text } from "react-native"; + +type Post = { + id: number; + title: string; + body: string; +}; + +export const loader: LoaderFunction<{ post: Post }> = async (request, params) => { + const id = params.id as string; + const response = await fetch(`https://api.example.com/posts/${id}`); + + if (!response.ok) { + throw new StatusError(404, `Post ${id} not found`); + } + + const post: Post = await response.json(); + return { post }; +}; + +function PostContent() { + const { post } = useLoaderData(); + + return ( + + {post.title} + {post.body} + + ); +} + +export default function PostDetail() { + return ( + }> + + + ); +} +``` + +Catch-all routes access `params.slug` the same way: + +```tsx +// app/docs/[...slug].tsx +import { type LoaderFunction } from "expo-server"; + +type Doc = { title: string; content: string }; + +export const loader: LoaderFunction<{ doc: Doc }> = async (request, params) => { + const slug = params.slug as string[]; + const path = slug.join("/"); + const doc = await fetchDoc(path); + return { doc }; +}; +``` + +Query parameters are available via the `request` object (server output mode only): + +```tsx +// app/search.tsx +import { type LoaderFunction } from "expo-server"; + +export const loader: LoaderFunction<{ results: any[]; query: string }> = async (request) => { + // Assuming request.url is `/search?q=expo&page=2` + const url = new URL(request!.url); + const query = url.searchParams.get("q") ?? ""; + const page = Number(url.searchParams.get("page") ?? "1"); + + const results = await fetchSearchResults(query, page); + return { results, query }; +}; +``` + +## Server-Side Secrets & Request Access + +Loaders run on the server, so you can access secrets and server-only resources directly: + +```tsx +// app/dashboard.tsx +import { type LoaderFunction } from "expo-server"; + +export const loader: LoaderFunction<{ balance: any; isAuthenticated: boolean }> = async ( + request, + params, +) => { + const data = await fetch("https://api.stripe.com/v1/balance", { + headers: { + Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`, + }, + }); + + const sessionToken = request?.headers.get("cookie")?.match(/session=([^;]+)/)?.[1]; + + const balance = await data.json(); + return { balance, isAuthenticated: !!sessionToken }; +}; +``` + +The `request` object is available in server output mode. In static output mode, `request` is always `undefined`. + +## Response Utilities + +### Setting Response Headers + +```tsx +// app/products.tsx +import { setResponseHeaders } from "expo-server"; + +export async function loader() { + setResponseHeaders({ + "Cache-Control": "public, max-age=300", + }); + + const products = await fetchProducts(); + return { products }; +} +``` + +### Throwing HTTP Errors + +```tsx +// app/products/[id].tsx +import { StatusError, type LoaderFunction } from "expo-server"; + +export const loader: LoaderFunction<{ product: Product }> = async (request, params) => { + const id = params.id as string; + const product = await fetchProduct(id); + + if (!product) { + throw new StatusError(404, "Product not found"); + } + + return { product }; +}; +``` + +## Suspense & Error Boundaries + +### Loading States with Suspense + +`useLoaderData()` suspends during client-side navigation. Push it into a child component and wrap with ``: + +```tsx +// app/posts/index.tsx +import { Suspense } from "react"; +import { useLoaderData } from "expo-router"; +import { ActivityIndicator, View, Text } from "react-native"; + +export async function loader() { + const response = await fetch("https://api.example.com/posts"); + return { posts: await response.json() }; +} + +function PostList() { + const { posts } = useLoaderData(); + + return ( + + {posts.map((post) => ( + {post.title} + ))} + + ); +} + +export default function Posts() { + return ( + + + + } + > + + + ); +} +``` + +The `` boundary must be above the component calling `useLoaderData()`. On initial page load the data is already in the HTML, suspension only occurs during client-side navigation. + +### Error Boundaries + +```tsx +// app/posts/[id].tsx +export function ErrorBoundary({ error }: { error: Error }) { + return ( + + Error: {error.message} + + ); +} +``` + +When a loader throws (including `StatusError`), the nearest `ErrorBoundary` catches it. + +## Static vs Server Rendering + +| | Server (`"server"`) | Static (`"static"`) | +| -------------------- | ------------------------------- | --------------------------------- | +| **When loader runs** | Every request (live) | At build time (`npx expo export`) | +| **Data freshness** | Fresh on initial server request | Stale until next build | +| **`request` object** | Full access | Not available | +| **Hosting** | Node.js server (EAS Hosting) | Any static host | +| **Use case** | Personalized/dynamic content | Marketing pages, blogs, docs | + +**Choose server** when data changes frequently or content is personalized (cookies, auth, headers). + +**Choose static** when content is the same for all users and changes infrequently. + +## Best Practices + +- Loaders are web-only; use client-side fetching (React Query, fetch) for native +- Loaders cannot be used in `_layout` files — only in route files +- Use `LoaderFunction` from `expo-server` to type loaders that use params +- The request object is immutable — use optional chaining (`request?.headers`) as it may be `undefined` in static mode +- Return only JSON-serializable values (no `Date`, `Map`, `Set`, class instances, functions) +- Use non-prefixed `process.env` vars for secrets in loaders, not `EXPO_PUBLIC_` (which is embedded in the client bundle) +- Use `StatusError` from `expo-server` for HTTP error responses +- Use `setResponseHeaders` from `expo-server` to set headers +- Export `ErrorBoundary` from route files to handle loader failures gracefully +- Validate and sanitize user input (params, query strings) before using in database queries or API calls +- Handle errors gracefully with try/catch; log server-side for debugging +- Loader data is currently cached for the session. This is a known limitation that will be lifted in a future release diff --git a/.agents/skills/shadcn/SKILL.md b/.agents/skills/shadcn/SKILL.md new file mode 100644 index 0000000..8a76cc0 --- /dev/null +++ b/.agents/skills/shadcn/SKILL.md @@ -0,0 +1,241 @@ +--- +name: shadcn +description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset". +user-invocable: false +--- + +# shadcn/ui + +A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI. + +> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +## Current Project Context + +```json +!`npx shadcn@latest info --json 2>/dev/null || echo '{"error": "No shadcn project found. Run shadcn init first."}'` +``` + +The JSON above contains the project config and installed components. Use `npx shadcn@latest docs ` to get documentation and example URLs for any component. + +## Principles + +1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too. +2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table. +3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc. +4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`. + +## Critical Rules + +These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs. + +### Styling & Tailwind → [styling.md](./rules/styling.md) + +- **`className` for layout, not styling.** Never override component colors or typography. +- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`. +- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`. +- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`. +- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`). +- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries. +- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking. + +### Forms & Inputs → [forms.md](./rules/forms.md) + +- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout. +- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`. +- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** +- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state. +- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading. +- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control. + +### Component Structure → [composition.md](./rules/composition.md) + +- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`. +- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md) +- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden. +- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`. +- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`. +- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`. +- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load. + +### Use Components, Not Custom Markup → [composition.md](./rules/composition.md) + +- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`. +- **Callouts use `Alert`.** Don't build custom styled divs. +- **Empty states use `Empty`.** Don't build custom empty state markup. +- **Toast via `sonner`.** Use `toast()` from `sonner`. +- **Use `Separator`** instead of `
` or `
`. +- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs. +- **Use `Badge`** instead of custom styled spans. + +### Icons → [icons.md](./rules/icons.md) + +- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon. +- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`. +- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup. + +### CLI + +- **Never decode or fetch preset codes manually.** Pass them directly to `npx shadcn@latest init --preset `. + +## Key Patterns + +These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above. + +```tsx +// Form layout: FieldGroup + Field, not div + Label. + + + Email + + + + +// Validation: data-invalid on Field, aria-invalid on the control. + + Email + + Invalid email. + + +// Icons in buttons: data-icon, no sizing classes. + + +// Spacing: gap-*, not space-y-*. +
// correct +
// wrong + +// Equal dimensions: size-*, not w-* h-*. + // correct + // wrong + +// Status colors: Badge variants or semantic tokens, not raw colors. ++20.1% // correct ++20.1% // wrong +``` + +## Component Selection + +| Need | Use | +| -------------------------- | --------------------------------------------------------------------------------------------------- | +| Button/action | `Button` with appropriate variant | +| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` | +| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` | +| Data display | `Table`, `Card`, `Badge`, `Avatar` | +| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` | +| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) | +| Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` | +| Command palette | `Command` inside `Dialog` | +| Charts | `Chart` (wraps Recharts) | +| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` | +| Empty states | `Empty` | +| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` | +| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` | + +## Key Fields + +The injected project context contains these key fields: + +- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode. +- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive. +- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`. +- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one. +- **`style`** → component visual treatment (e.g. `nova`, `vega`). +- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props. +- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`. +- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc. +- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA). +- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`). + +See [cli.md — `info` command](./cli.md) for the full field reference. + +## Component Docs, Examples, and Usage + +Run `npx shadcn@latest docs ` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content. + +```bash +npx shadcn@latest docs button dialog select +``` + +**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing. + +## Workflow + +1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh. +2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed. +3. **Find components** — `npx shadcn@latest search`. +4. **Get docs and examples** — run `npx shadcn@latest docs ` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`. +5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below). +6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project. +7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on. +8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, etc.), ask which registry to use. Never default to a registry on behalf of the user. +9. **Switching presets** — Ask the user first: **reinstall**, **merge**, or **skip**? + - **Reinstall**: `npx shadcn@latest init --preset --force --reinstall`. Overwrites all components. + - **Merge**: `npx shadcn@latest init --preset --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually. + - **Skip**: `npx shadcn@latest init --preset --force --no-reinstall`. Only updates config and CSS, leaves components as-is. + - **Important**: Always run preset commands inside the user's project directory. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base ` explicitly — preset codes do not encode the base. + +## Updating Components + +When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.** + +1. Run `npx shadcn@latest add --dry-run` to see all files that would be affected. +2. For each file, run `npx shadcn@latest add --diff ` to see what changed upstream vs local. +3. Decide per file based on the diff: + - No local changes → safe to overwrite. + - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications. + - User says "just update everything" → use `--overwrite`, but confirm first. +4. **Never use `--overwrite` without the user's explicit approval.** + +## Quick Reference + +```bash +# Create a new project. +npx shadcn@latest init --name my-app --preset base-nova +npx shadcn@latest init --name my-app --preset a2r6bw --template vite + +# Create a monorepo project. +npx shadcn@latest init --name my-app --preset base-nova --monorepo +npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo + +# Initialize existing project. +npx shadcn@latest init --preset base-nova +npx shadcn@latest init --defaults # shortcut: --template=next --preset=base-nova + +# Add components. +npx shadcn@latest add button card dialog +npx shadcn@latest add @magicui/shimmer-button +npx shadcn@latest add --all + +# Preview changes before adding/updating. +npx shadcn@latest add button --dry-run +npx shadcn@latest add button --diff button.tsx +npx shadcn@latest add @acme/form --view button.tsx + +# Search registries. +npx shadcn@latest search @shadcn -q "sidebar" +npx shadcn@latest search @tailark -q "stats" + +# Get component docs and example URLs. +npx shadcn@latest docs button dialog select + +# View registry item details (for items not yet installed). +npx shadcn@latest view @shadcn/button +``` + +**Named presets:** `base-nova`, `radix-nova` +**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) +**Preset codes:** Base62 strings starting with `a` (e.g. `a2r6bw`), from [ui.shadcn.com](https://ui.shadcn.com). + +## Detailed References + +- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states +- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading +- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects +- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index +- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion +- [cli.md](./cli.md) — Commands, flags, presets, templates +- [customization.md](./customization.md) — Theming, CSS variables, extending components diff --git a/.agents/skills/shadcn/agents/openai.yml b/.agents/skills/shadcn/agents/openai.yml new file mode 100644 index 0000000..ab636da --- /dev/null +++ b/.agents/skills/shadcn/agents/openai.yml @@ -0,0 +1,5 @@ +interface: + display_name: "shadcn/ui" + short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI." + icon_small: "./assets/shadcn-small.png" + icon_large: "./assets/shadcn.png" diff --git a/.agents/skills/shadcn/assets/shadcn-small.png b/.agents/skills/shadcn/assets/shadcn-small.png new file mode 100644 index 0000000000000000000000000000000000000000..547154b97f2298335159c23eec1dac0d147a1246 GIT binary patch literal 1049 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBDAAG{;hE;^%b*2hb1<+n z3NbJPS&Tr)z$nE4G7ZRL@M4sPvx68lplX;H7}_%#SfFa6fHVkr05M1pgl1mAh%j*h z6I`{x0%imor0saV%ts)_S>O>_%)r1c48n{Iv*t(uO^eJ7i71Ki^|4CM&(%vz$xlkv ztH>0+>{umUo3Q%e#RDspr3imfVamB1>jfNYSkzLEl1NlCV?QiN}Sf^&XRs)CuG zfu4bq9hZWFf=y9MnpKdC8&o@xXRDM^Qc_^0uU}qXu2*iXmtT~wZ)j<0sc&GUZ)Btk zRH0j3nOBlnp_^B%3^4>|j!SBBa#3bMNoIbY0?6FNr2NtnTO}osMQ{LdXGvxn!lt}p zsJDO~)CbAv8|oS8!_5Y2wE>A*`4?rT0&NDFZ)a!&R*518wZ}#uWI2*!AU*|)0=;U- zWup%dHajlKxQFb(K%V94;uvBfm>Yb$>yU#$kCbgiC(9RwrlwA(rsW(``(6H=zq@!* zUB_~Trk_lTdxPh$yxDTsVY&L;b+6{tRm^+d+gl^OQcCIBok=%y)Vhx@dR1ceL3r{0 z9=2nMXx8DxEkhL{v?(>~8{qDBkfB!u-iDB`| z-5d%Ae82YI%n8Zg9$mRN?o-|V)V*=5b-R>a>}P2{=pcK^B(&B-W{C#Z!Q%b*ueK>n zK6ymYLRneebCKDSzcqHEHFopo-mkH{zIkt2Z0^086Tko7%N)NxoZYSDM1jSUwPpuS zSeq*wzrC2D(tq4pZ#s9QP@Cd8Q^wW3*2cL_519?*;#4`B;+ei#o;C{Kkamm3Jooa; zE$3K-+c(6ShTjelW_B*W5o zcS6z34IR5C-SIF8m(lui^IOy!-~We?b1?PSGniCYR=ZyR^%<1lJzf1=);T3K0RU@N BSy=!8 literal 0 HcmV?d00001 diff --git a/.agents/skills/shadcn/assets/shadcn.png b/.agents/skills/shadcn/assets/shadcn.png new file mode 100644 index 0000000000000000000000000000000000000000..b7b6814acc25073e5f48099b1fd3f70c47bfb1c3 GIT binary patch literal 3852 zcmY*c2{@Ep*nY=Y1|x&SAZEs{m}!)<%wS9yONAB@M)rMZgo-eODf<#aqEe(qiK0X# zjeV<7Sz@e_J-dH=Uw_~KpZ7ZNbD!tj&;6X|T<3bv^oi2VQ{31CN!jQ|snzFQu@Q;0Du?| zfb$Q>hHZD6F}v@?{A7+^3dW)SVQ8`OjvI|a z{6nF7;ZWA*ClCgH0WJs)HH;bth37*c5IOtVpZ7;)e9lMRL5Xd-wU$;iu|t*(dB|ufCq)@;pdCk(RDoU7f8jS zP&Fw%scw{13LdO|DW$q z9v$>f^8d4#e=GgR%Ptkqr-S~xZFoMo{SXfTK;ub7{gc641VugDjPn7=AJBO!*mUKucc{7ey~li!vybaIkF z8OboYVX%y|2%P`k*m&i}s=eW8)0q&YJ<-thLrc)HZ?6uc7Fd!$(lWSvH;?Y^+|N2q z(lz!>Ip|<{_a4CnLN%GIxW|*Sl|;4c3?=Q>z5fWEEtp(&BzeAccRS{zf9!64+SAA{ zhe+CT7U8Ox1UUnilJqweOM{d}_w6-|yz}dA=-R7yHrxs|(MLR_`U=AC%0V#PpIuxs z`__xodV3Bl2ayP^m8isMu4iD<`=4UURtHBPsaLp81r-f3$(4vZbRPd}*>wTo@@?zp zjl1KkifF5p%-V<}MTk3`()wm7qcMd8<14WE>Y_YwjB;{Zt!@vJbH&pu!Ye!p{1kAj zlRg2pkl)WBbyj>3e<$)@mP?WW}W-c;_(UQ(}=MJLED9F^U*P1KWxu z1h@&s1?M0%ax_P5Qb>kkPKEH=OKGZn z&-@5MD^|kk?Hzkfz8h$8#{$$OjAyr!+uGtp>ns1^oS7Nlv6k$^tzTdIb%jUtI8F3t zL;LuqJ7-@4UI|Q)HN=Ap;dAmuZY4c&XphavC{kl z)mMY6L)S-94;V|YT3TA7U^;uvS0W!$W`JIb@kNAm0@IKVWJ@R_jgX6wlUd5V%pHIn;=Z~px z-B_MFLx+ldzTQUp5C zkXaznhH18TytzOgD%$iMnLiAtjvnHLkM;?P*3ZFb#cbY&ZF*|R;4V4$85DZMQ#T@| zxuMMPg?cl|hNJ$2S&%fLErMQSzCnzc_QGh3x@I8V&Omf&=_n+EaC}tft^`)^W5-=1 z0bbJ#wK8+=zNaz-V{U?NtJp&%IDd9-xIut--L}@gU{E>B&ym$vT2@wEZq- zzDqmDf)Cr-aZ-5KIYF!Elcq_X%?qXsg}Yhp=^bebcZ-Ucd$p+US;P}(@VrR4_fy6!qx?X)%SIi+LRsDR` zQ8+xRd{In4WLlVjoy5ElKt%?Ykr)cH*G}0^tE=$(#0W?l)_9MV89|sem>Y}t&?fGt zo3o0sdsG9?JhC1-KM}e*&zeb*H$7JoD&0z#=2sH725JmGV`WoVb86exi5p^Lm{B5BlZRd6(bKH`QmJ*u8#(j6IH1 zFf;Li5iqsV=V^irlzzwVJ$xXwz?I8y`i+t-g0?n77Ie}-fBp1{)&Dm1Yk^Fb=7rI4 zAD)SQRv9=Xr%G{qI!)}>rf;lzG))%89I~zF)R~&_Ur)%0>4+7bxbBknHT`^+jNFe%p=t<=q}3W1Y-u+ zi-cN{V#+HsEEB8UQsUt1uE991MvN%&V?(0Mcb~82H!ori&rLm~Xyf>i#u_t=rzHA3 zu?NRljnv7YU;T&V#(OVE$+=^@kaXvs%C3f78E(k$IyZ`)N$NYtLMfphBkV|oSI}7N zEpFrKnA*jL!k5P(%)+Sl^e>hI@Y}jqmvb~@RD`9$!9Jpu-Wf2Cq?hfV{ghuo&AG>9 zcvC>&o43J*WfyXuRWV)Y0~9;i68pPyv&~-%Ysp}xCY)|oF5?x{w~aS zfx&5VFU)gcnMk*q%=jkB9w_qQ)x=v#1Q^_3`uyEfKiD4y$vaOR4n4IpvQmyNXK%#R zU|XV~4Bq=Kr7Zp3#hdF5_MgA#yL-jJ4E5*j2?mhMq17{mD#6f?!#-b>@HA&~uECub zF2reQA9f>OHLzqeZhi$%7fW!n^Y3_Y{C0vfQs0?a(Pcz7Opuz;(9n>$6)c9L`((v~ z@R1+M2kRN}mDuKCk=od(UwR?y%hUbEv$dC0BD&Ir;03Mf&|A#ZFtn&*mnkH8>2w0+imbwLLU77q!A=W ztt$OR$b#$dkIAwM*ZWG{5*zUPkBN$42Kxe)AYe9DV+?(A^_|IoJLzRGKlbpb%n=PVmB`nxX< z)_}Tpg&IoX;4<0!ksDY9f>*dl`c}zSl!(+ujG#<&yu3AEKR)zVKTaK88*+ma)}3)G zx460(7TNMTz(t&DUv)`7lj(r=`!?>~Q}XW3w-sipV!{Trr8`zsvu9NEK9)zO0>v3D zOs=r|`OyjAS{Ea@UIRrr2Y7A?5+5}R4RpF)ee5^F-dhvP6MKyO`kNB4jGC{-$x}`x zXmpAxVV=WR{>!zDxoq7#DN+_BWG_PRr?$Sa(XT@zUL`HuM{I4bZSuo97aF&7tL@4P zO59)SaLA2ZMU3dO5>=P)GiqcIBUy4A2h_IC-7+IROG<+Qqczgo-JyHw-!h2%h8#$y zr;y4>z2J1R4+`7o>#OWA2iiS1m8)q6A}u3d7VBk **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag. + +## Contents + +- Commands: init, add (dry-run, smart merge), search, view, docs, info, build +- Templates: next, vite, start, react-router, astro +- Presets: named, code, URL formats and fields +- Switching presets + +--- + +## Commands + +### `init` — Initialize or create a project + +```bash +npx shadcn@latest init [components...] [options] +``` + +Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step. + +| Flag | Short | Description | Default | +| ----------------------- | ----- | --------------------------------------------------------- | ------- | +| `--template