HaloStack Docs
Open App
Apps

Web

The TanStack Start front end — file-based routing, server functions, and the shared UI library, on React 19 + Vite.

apps/web is the primary user-facing front end: a TanStack Start application built on TanStack Router, Vite, and React 19. It serves on port 3001. This is deliberately not a Next.js app — there is no app/ directory and no React Server Components, so patterns you might reach for from Next.js do not apply here.

Stack

The app is assembled from a small, focused set of tools:

  • TanStack Start (@tanstack/react-start) plus TanStack Router (@tanstack/react-router) for the framework and routing.
  • Vite as the build tool, configured with @tanstack/router-plugin, @tailwindcss/vite, and @vitejs/plugin-react.
  • React 19, Tailwind CSS v4, and shadcn/ui delivered through the shared @halostack/ui package.

Routing

Routing is file-based. Every file under src/routes/ maps directly to a URL — the file path is the route. The table below shows the conventions you will encounter most often:

Two files anchor the router itself. src/routeTree.gen.ts is generated by the router plugin and regenerates on every dev and build run — never edit it by hand. src/router.tsx builds the router via getRouter() and registers the route types through a declare module "@tanstack/react-router" block.

Adding a route

To add a page, create a file under src/routes/ and export a Route built with createFileRoute. The router plugin picks it up automatically:

A few conventions keep route code consistent:

  • Data loading: fetch in the route loader and read it back with Route.useLoaderData(), rather than fetching from a useEffect.
  • Server logic: wrap server-only code in createServerFn() from @tanstack/react-start. There are no instances of this yet — follow this pattern for the first one.
  • Navigation: move between routes with Link or useNavigate() from @tanstack/react-router, both typed against the generated route tree.

Forms

Build forms with the app-local form kit — useAppForm from @/lib/form/use-app-form, composed with field.TextField — rather than a bare form element wired up with useState. Mark mandatory fields with required (which renders the * marker and sets aria-required), set autoComplete and type where they apply, validate with zod through validators, and surface inline errors through FieldError with match={true}. The toast helpers require a description, so every notification carries supporting copy.

The full rules live in .agents/rules/forms.md and .agents/rules/interactions.md. For working examples, see src/components/todos/todo-create-form.tsx, src/components/account/security-section.tsx, and the forms under src/routes/auth/*.

Imports and styling

Two path aliases keep imports clean and are resolved by Vite's tsconfigPaths:

  • @/* points at app-local files under apps/web/src/*.
  • @halostack/ui/* points at the shared UI library.

Styling is Tailwind v4 together with the cn helper from @halostack/ui/lib/utils. Global styles come from @halostack/ui/globals.css, imported once in __root.tsx.