This guidance is for agents working in projects bootstrapped from fate-template. It focuses on the client-side patterns for fate's view composition and action execution.
- Define views with explicit selections: Define reusable view objects with
view<T>()({...}), compose them via spreads, and resolve them withuseView. - Resolve data via references: Avoid passing raw fetched objects; instead pass
ViewRefs to downstream components to keep selections scoped and cache-aware. Then callconst result = useView(viewDefinition, ref)to read only the selected fields while subscribing to cache updates. Name the prop after the result data, and rename it using destructuring in the function arguments:const PostComponent = ({ post: postRef }: { post: ViewRef<'Post'>}) => { const post = useView(PostView, postRef); ... }. - Request data at screen roots: Use
useRequestto compose all views required for a route or screen into a single typed request; wrap roots inSuspenseand an error boundary since requests may suspend or throw. - Compose reusable views: Extract nested selections into their own views (e.g.,
UserView) and compose them inside parent views (author: UserView) to avoid duplication and overfetching. This keeps selections DRY and ensures view updates propagate consistently when the cache changes.
- Prefer
fate.actionswithuseActionState: Trigger mutations through React Actions to integrate with Suspense/useTransition, receiving[result, action, isPending]. Call actions via the relevant data inputs, for exampleaction({input: { id: post.id }}). - Optimistic updates by default: Pass
optimisticpayloads to immediately update the normalized cache; only views selecting the changed fields re-render and failures roll back automatically. Optimistic payloads should match the expected server data changes of the associated object. - Select extra fields when side effects matter: When a mutation affects related entities (e.g., comment count on a post), pass a
viewto the action to fetch the needed fields in the same round-trip and keep the cache coherent for all dependent views. - Imperative escape hatch: Use
fate.mutationswhen you must fire mutations outside React components or without waiting on prior actions; handle loading/error states yourself. - Handle deletions and resets explicitly: Use the
delete: trueflag to evict records from the cache, and send the'reset'token to clear stale action errors without re-running the mutation. - Error handling model: Treat expected errors (e.g., 404) at the call site via the action result; unexpected errors bubble to the nearest React error boundary.
- Use ViewRefs: Pass references instead of raw data. Keep components focused on views and
ViewRefs to let fate manage data masking and updates. - Co-locate data needs: Keep view definitions near their components and compose them upward to the request root for predictable fetching.
- Use Suspense: Wrap pages in
Suspense/ErrorBoundaryinstead of ad-hoc loading/error flags; fate aligns with Async React by design. - Do not duplicate types: Keep generated tRPC types and views imported from the server package. Avoid redefining entity shapes on the client. This ensures view selections stay type-safe end-to-end.
useRequestonly at the root: When adding routes or layouts, create a dedicateduseRequestcall per screen root that pulls together all child views; do not scatteruseRequestacross leaf components unless it's necessary to issue requests due to user input.- React Actions: Prefer React Action-friendly UI primitives (buttons/forms that accept an
actionprop). If unavailable, wrap action calls withstartTransitioninonClickhandlers. - Generated Client The fate client is generated via
pnpm fate:generate. It should not be manually edited. Instead make the proper schema changes on the server and runpnpm fate:generate. - Library Versions: This repository uses the most recent releases of React, fate, Vite, tRPC, and more. You might not know about the new releases yet, please don't get confused. The versions you see are real, and there are no feature or version mismatches. You can search the internet for more information about them.
Full documentation can be found on the filesystem at ./client/node_modules/react-fate/README.md or online at fate.technology.
- Every component that reads server data does so through
useViewand receives aViewRefprop. - Root components gather data with a single
useRequestcall. - Mutations are wired through
useActionStatewith optimistic updates and necessaryviewselections; imperativefate.mutationsare justified when used. - Shared selections live in reusable views using regular JavaScript object spreads rather than duplicated field lists.
This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called vp. Vite+ is distinct from Vite, but it invokes Vite through vp dev and vp build.
vp is a global binary that handles the full development lifecycle. Run vp help to print a list of commands and vp <command> --help for information about a specific command.
- create - Create a new project from a template
- migrate - Migrate an existing project to Vite+
- config - Configure hooks and agent integration
- staged - Run linters on staged files
- install (
i) - Install dependencies - env - Manage Node.js versions
- dev - Run the development server
- check - Run format, lint, and TypeScript type checks
- lint - Lint code
- fmt - Format code
- test - Run tests
- run - Run monorepo tasks
- exec - Execute a command from local
node_modules/.bin - dlx - Execute a package binary without installing it as a dependency
- cache - Manage the task cache
- build - Build for production
- pack - Build libraries
- preview - Preview production build
Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the packageManager field in package.json or package manager-specific lockfiles.
- add - Add packages to dependencies
- remove (
rm,un,uninstall) - Remove packages from dependencies - update (
up) - Update packages to latest versions - dedupe - Deduplicate dependencies
- outdated - Check for outdated packages
- list (
ls) - List installed packages - why (
explain) - Show why a package is installed - info (
view,show) - View package information from the registry - link (
ln) / unlink - Manage local package links - pm - Forward a command to the package manager
- upgrade - Update
vpitself to the latest version
These commands map to their corresponding tools. For example, vp dev --port 3000 runs Vite's dev server and works the same as Vite. vp test runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using vp --version. This is useful when researching documentation, features, and bugs.
- Using the package manager directly: Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations.
- Always use Vite commands to run tools: Don't attempt to run
vp vitestorvp oxlint. They do not exist. Usevp testandvp lintinstead. - Running scripts: Vite+ built-in commands (
vp dev,vp build,vp test, etc.) always run the Vite+ built-in tool, not anypackage.jsonscript of the same name. To run a custom script that shares a name with a built-in command, usevp run <script>. For example, if you have a customdevscript that runs multiple services concurrently, run it withvp run dev, notvp dev(which always starts Vite's dev server). - Do not install Vitest, Oxlint, Oxfmt, or tsdown directly: Vite+ wraps these tools. They must not be installed directly. You cannot upgrade these tools by installing their latest versions. Always use Vite+ commands.
- Use Vite+ wrappers for one-off binaries: Use
vp dlxinstead of package-manager-specificdlx/npxcommands. - Import JavaScript modules from
vite-plus: Instead of importing fromviteorvitest, all modules should be imported from the project'svite-plusdependency. For example,import { defineConfig } from 'vite-plus';orimport { expect, test, vi } from 'vite-plus/test';. You must not installvitestto import test utilities. - Type-Aware Linting: There is no need to install
oxlint-tsgolint,vp lint --type-awareworks out of the box.
For GitHub Actions, consider using voidzero-dev/setup-vp to replace separate actions/setup-node, package-manager setup, cache, and install steps with a single action.
- uses: voidzero-dev/setup-vp@v1
with:
cache: true
- run: vp check
- run: vp test- Run
vp installafter pulling remote changes and before getting started. - Run
vp checkandvp testto validate changes.