frontend-standards
SKILL.md
Frontend Standards (Seagull)
When to use
- Building or refactoring React Native screens/components in
apps/expo. - Moving logic into
apps/expo/src/businessmodules. - Deciding where logic/state/style belongs (business vs UI).
Core rules
- Business logic lives in
apps/expo/src/business/<module>/<feature>.- Files:
hooks.ts,effect.ts(side effects only),store.ts(Zustand shared state),types.ts,utils.ts. - Business modules must not contain UI details (className/layout/components).
- Files:
- UI layer is pure: no
trpc,queryClient,authClient,useQuery/useMutation. - Hook contract:
- UI events call business methods; UI does not assemble payloads.
- Derived data computed in hooks.
- Side effects only in
effect.ts, mounted once by page-level aggregate hook.
- Store access: UI may read store via selector only. No side effects in UI.
- Styling: TailwindCSS + NativeWind with
className.classNamecomposition must useclsx.- Use semantic tokens (
text-foreground,bg-primary,border-border), avoid hardcoded colors.
React Query / tRPC (TanStack Query) rules
- Only business layer uses Query/Mutation: keep
useQuery/useMutation,queryClient.invalidateQueries(...)insideapps/expo/src/business/**. - Never put the whole
useMutation(...)result object intouseEffectdeps (it is not stable and can cause effect re-run loops).- Prefer destructuring:
const { mutate, mutateAsync, isPending } = useMutation(...), then depend onmutateAsync/mutateonly if needed. - If you must reference mutation from async callbacks/timers/cleanup, prefer
useReforstore.getState()over closures.
- Prefer destructuring:
- Query drives state; mutations only invalidate:
- Prefer “server truth → store flags” to be driven from query result (single source of truth).
- Mutations should generally
invalidate/refetchqueries inonSuccess/onSettled, not directly flip multiple store flags.
- Guard query/mutation with
enabledand stable inputs:- All query inputs must be stable and non-empty; use
enabled: isAuthed && !!id && !sessionLoadingstyle gating. - Avoid calling mutation in render; trigger from event or a guarded effect.
- All query inputs must be stable and non-empty; use
- Avoid infinite loops:
- If an effect calls a mutation that invalidates a query, ensure the effect does not re-trigger solely because the query updated.
- Use “attempt once per
id” refs for auto-acquire patterns; never spam retries on every re-render.
- TTL/lock heartbeats:
refetchIntervalrefreshes query data, but does not extend TTL unless backend explicitly does so.- If backend has a
refreshmutation, keep it and run it on an interval while owner; then invalidategetto syncexpiresAt.
- Cleanup must read latest state:
- For unmount releases, do not trust closed-over
hasLock; useuseTripEditStore.getState()(or a ref) to check latest ownership before callingrelease.
- For unmount releases, do not trust closed-over
Refactoring checklist
- Page contains only UI logic and view state.
- Repeated logic extracted to business hooks/utils/components.
- Files > 500 lines are split by feature.
- Lists handle empty states.
Canonical reference
- See
skills/frontend-standards/references/frontend-standards.mdfor the full policy text and examples.
Weekly Installs
6
Repository
clarkkkk/seagullFirst Seen
Jan 28, 2026
Security Audits
Installed on
opencode6
gemini-cli6
cursor6
antigravity5
windsurf5
codebuddy2