Skip to content

FREEZONEX/monoapptemplate

Repository files navigation

MonoApp Scaffold

A scaffold built on TanStack Start 1.x + Drizzle ORM + TailwindCSS 4 for building shop-floor MES applications on the Tier0 platform. The goal is to give agents a light but complete starting point: authentication, database connectivity, platform deployment, build outputs, and layout contracts are pre-wired, while business pages and app-local components are generated for the target application instead of being constrained by a fixed template library.

Tech Stack

Layer Technology
Framework TanStack Start 1.x (Vite 8/Rolldown + TanStack Router, React 19 SSR)
ORM Drizzle ORM + node-postgres
Validation Zod (drizzle-zod derives schemas and types)
Styling TailwindCSS 4 (Vite plugin, no PostCSS) + Tier0 design tokens
UI Lightweight layout contracts + Tailwind utilities + Tier0 tokens; no prebuilt business component library
Charts Recharts 3 (must be wrapped in <ResponsiveContainer>)
Tables TanStack React Table 8
Drag and drop dnd-kit
Tier0 Platform SDK @tier0/sdk (OpenAPI REST + MQTT over WebSocket)
Icons Lucide React
Toasts Sonner (Toaster is mounted in __root.tsx)
Motion motion imported from @/lib/motion, never directly from motion/react

Quick Start

npm install
# Configure DATABASE_URL in .env
npx drizzle-kit push      # Optional: pre-sync schema locally; services can also bootstrap at runtime
npx tsx src/db/seed.ts    # Optional: explicit seed / fixture reset
npm run dev               # -> http://localhost:5173

When the agent runs inside the Executor Pod, do not run npm install manually. The platform manages installs automatically, and manual installs can conflict with the shared volume. See AGENTS.md.

Environment Variables

There is no explicit mode switch. If a variable is missing, the scaffold falls back to local defaults. If the variable is set, the corresponding capability is enabled.

Variable Required When unset When set
DATABASE_URL Connects to PostgreSQL
SESSION_SECRET Production ✅ Random local value generated at boot HMAC signing key for the session cookie; production must use 32+ chars
DIRECT_DATABASE_URL Direct database connection for drizzle-kit push and seed.ts
DB_SCHEMA Uses public Runs all queries inside the specified schema
APP_ID Defaults to "monoapp" Returned by /api/manifest as the app id
VITE_BASE_PATH No URL prefix Applied to Vite base, router basepath, and apiUrl()
NEXT_PUBLIC_BASE_PATH Legacy fallback only Used by apiUrl() when VITE_BASE_PATH is absent

Tier0 SDK auth and connection variables are injected by the platform at deploy time. Do not write them into .env.example, and do not expose UI in the scaffold or generated apps for users to configure SDK auth, API keys, tokens, OpenAPI hosts, MQTT hosts, or workspace bindings. Those values belong to the SDK, the platform gateway, or the runtime environment.

Variables Injected for the SDK

Variable Purpose
TIER0_API_HOST OpenAPI service host used by @tier0/sdk/openapi
TIER0_API_KEY API credential used by both OpenAPI and MQTT
TIER0_MQTT_HOST MQTT WebSocket broker host
TIER0_MQTT_PORT MQTT WebSocket port, default 8084

If the platform explicitly requires browser-side Vite env access, the runtime is responsible for injecting VITE_TIER0_*. Generated apps should not turn those values into user-editable settings.

Tier0 SDK SSR Compatibility

@tier0/sdk@0.1.3 ships as a dual ESM/CJS package. This scaffold still keeps SDK access behind server-side lazy loaders so preview startup and normal SSR do not execute optional platform I/O during page initialization. The SSR policy is pinned in vite.config.ts:

ssr: {
  external: ["pg", "@tier0/sdk", "mqtt"],
}

SDK calls are loaded on the server through the lazy helper in src/lib/tier0.ts. Generated apps should not top-level import SDK submodules, should not move the SDK into ssr.noExternal, and should not bypass the SDK with fallback MQTT clients or hand-written fetch wrappers.

For deployment details, see docs/platform-integration.md.

Project Structure

src/
  start.ts                    <- TanStack Start global request middleware (SSO gateway, do not modify)
  router.tsx                  <- Router factory exporting getRouter() (do not modify)
  routeTree.gen.ts            <- Generated by @tanstack/router-plugin (gitignored, do not edit)
  styles/globals.css          <- TailwindCSS 4 + theme tokens + keyframes (do not replace)
  routes/                     <- Interface layer (HTTP): file names define URLs and nesting
    __root.tsx                <- HTML document shell with Toaster (do not modify)
    _app.tsx                  <- Workspace layout with Shell for management/planning/analytics/admin
    _app.index.tsx            <- Blank "/" scaffold placeholder; finished apps must replace or redirect it
    station.tsx               <- Station layout without sidebar for /station/* task-first workflows
    review.tsx                <- Review layout without sidebar for /review/* evidence/approval flows
    login.tsx                 <- Role selection page outside Shell
    api/                      <- Server routes: thin wrappers that delegate to services/
      health.ts               <- Health check
      manifest.ts             <- Role manifest (do not modify)
      auth/{select-role,me,logout}.ts <- Cookie auth handlers (do not modify)
  services/                   <- Domain layer: business rules, state machines, multi-step transactions
  components/
    Shell.tsx                 <- Left navigation shell; update defaultModules here
    layouts/                  <- StationLayout, ReviewLayout, MonitorLayout, and similar shell contracts
    login-role-selector.tsx   <- Client-side login buttons (do not modify)
    toaster.tsx               <- Sonner mount point
    client-only.tsx           <- Hydration boundary for SSR-incompatible libraries
    <domain>/                 <- App-local components generated per business need
  db/{index,schema,seed}.ts   <- Data layer: Drizzle client, table definitions, seed script
  lib/                        <- Cross-cutting utilities, not domain logic
    auth.ts                   <- getCurrentUser() / requireAuth() (do not modify)
    gateway.ts                <- Gateway header parsing (do not modify)
    route-handlers.ts         <- withErrors() response/error wrapper (do not modify)
    users.ts                  <- AppUser type (do not modify)
    permissions.ts            <- Permission matrix definition
    hooks.ts                  <- Polling and request hooks
    motion.ts                 <- motion/react re-export
    tier0.ts                  <- Server-side lazy loader for @tier0/sdk
    utils.ts                  <- cn(), apiUrl()
server.mjs                    <- Production Node HTTP entrypoint (do not modify)
vite.config.ts                <- TanStack Start + Tailwind v4 + Vite 8 paths + Tier0 SDK SSR external rules

Route File Naming

TanStack Router is file-based:

File URL Notes
routes/__root.tsx Wraps every page
routes/_app.tsx Workspace layout for management/planning/analytics/configuration
routes/station.tsx /station Station layout for scan/tap/confirm execution
routes/review.tsx /review Review layout for exceptions/quality/approvals
routes/_app.index.tsx / Blank scaffold placeholder; finished apps must replace or redirect it
routes/_app.work-orders.tsx /work-orders Under _app
routes/station.receiving.tsx /station/receiving Under station
routes/review.exceptions.tsx /review/exceptions Under review
routes/_app.work-orders.$id.tsx /work-orders/:id Dynamic param
routes/login.tsx /login Outside _app, no Shell
routes/api/work-orders.ts /api/work-orders Server route
routes/api/work-orders/$id.ts /api/work-orders/:id Nested server route
  • _ prefix: pathless, contributes no URL segment
  • . separator: URL nesting
  • $ prefix: dynamic param
  • Choose layout by workflow: station for execution/scan flows, review for review/approval flows, _app for management/analytics work
  • If built-in layouts do not fit, create a new prefixed layout such as monitor.tsx, wizard.tsx, portal.tsx, or editor.tsx

Reading Dynamic Params

// src/routes/_app.work-orders.$id.tsx
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";

export const Route = createFileRoute("/_app/work-orders/$id")({
  validateSearch: z.object({ tab: z.enum(["details", "history"]).optional() }),
  component: Page,
});

function Page() {
  const { id } = Route.useParams();
  const { tab } = Route.useSearch();
  return <div>Order {id}, tab: {tab ?? "details"}</div>;
}

The matching API route is src/routes/api/work-orders/$id.ts, where the handler reads params.id from the first argument.

Add a New Page

Only workspace pages belong under _app and appear in Shell.tsx defaultModules. The scaffold keeps exactly one intentionally blank route, _app.index.tsx, on purpose. A finished app must replace / with a real dashboard, a redirect, or the actual entry experience. If the app has only one primary page, make that page own / instead of leaving / blank and placing the only real screen at a secondary route.

// src/routes/_app.work-orders.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_app/work-orders")({
  component: WorkOrdersPage,
});

function WorkOrdersPage() {
  return <div className="p-6">Orders</div>;
}

Task-first station pages belong under station and should not appear in the sidebar:

// src/routes/station.receiving.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/station/receiving")({
  component: ReceivingPage,
});

If the workflow needs a fundamentally different interaction model, create a custom layout such as src/routes/monitor.tsx with src/components/layouts/MonitorLayout.tsx. Custom layouts should stay minimal and should not preload business cards, charts, or forms. Do not create an empty pathless layout with no child pages, because it will conflict with /.

Single-experience apps such as monitor, station, review, or kiosk should not keep the default sidebar home page. / must route directly to the real app experience.

Add an API Endpoint

Step 1: Write the service in src/services/work-orders.ts. This is where business logic, state machines, and transactions belong. The db client may only appear in services/ and db/seed.ts. Each implemented module should define runtime bootstrapModule(...) behavior near the top of the service so preview and new tenant schemas can create tables, indexes, and baseline data on first use. Shared bootstrap helpers create all module schemas/tables/indexes first and seed afterward; do not hand-roll local create/seed ordering in each service.

Step 2: Write the route in src/routes/api/work-orders.ts. Keep it thin: requireAuth() -> schema.parse() -> service call -> Response.json(...), wrapped in withErrors(...). Throw HttpError for client-facing failures or let Zod issues bubble naturally.

For a full state-machine example with multi-step db.transaction(...) logic and Zod validation, see AGENTS.md §Build Order — Step 3.

In pages, always call fetch(apiUrl("/api/work-orders")). Do not hardcode /api/... without apiUrl() or deployments with a base path will break.

Three-Layer Architecture

Browser
  |
  |  fetch(apiUrl("/api/..."))
  v
src/routes/api/**.ts          <- Interface layer: requireAuth -> Zod.parse -> service -> Response.json
  |                               Wrapped by withErrors for {status,message} / Zod issue mapping
  v
src/services/**.ts            <- Domain layer: business logic, state machines, multi-step transactions
  |                               Pure TypeScript, no HTTP concepts
  v
src/db/{index,schema}.ts      <- Data layer: Drizzle client + table definitions
  v
PostgreSQL

Hard rules:

  • db may only be imported from src/services/** and src/db/seed.ts
  • Request, Response, and Headers belong only in routes, src/start.ts, and src/lib/{auth,gateway,route-handlers}.ts
  • Multi-step writes (2+ DB operations) must run inside db.transaction(async tx => ...)
  • Service entry points should await their module runtime bootstrap first
  • Server cookie/header helpers such as getCookie or getRequest belong only in server route handlers, createServerFn bodies, or src/start.ts

Authentication (SSO)

The platform gateway injects the user identity headers. The app does not manage passwords or a user-account table.

Supported gateway header formats:

# Format 1: JSON
user: {"userID":"u123","userName":"mercy","email":"m@x.com"}

# Format 2: separate headers
X-App-User-ID: u123
X-App-User-Name: mercy
X-App-User-Email: m@x.com

# Format 3: minimum set
X-App-User-ID: u123

Flow:

  1. Gateway injects headers into the request
  2. src/start.ts middleware checks the request
  3. Existing mes-session cookie -> continue
  4. Gateway header without cookie -> redirect to /login?from=...
  5. User selects role -> POST /api/auth/select-role writes the cookie
  6. Later requests enforce role access via requireAuth("admin")

Roles are app-owned. Define the role-to-action mapping in src/lib/permissions.ts as PERMISSION_MATRIX. The platform reads GET /api/manifest to build role-assignment UI.

See docs/platform-integration.md for the full platform model.

Scripts

npm run dev          # preview-compatible Vite dev server -> http://localhost:5173
npm run dev:local    # local Vite dev; port can drift if occupied
npm run dev:force    # rebuild the Vite dependency cache before starting
npm run typecheck    # TypeScript noEmit
npm run build        # Vite 8/Rolldown build -> dist/{client,server}
npm run build:check  # build + typecheck + lint
npm run start        # node server.mjs
npm run lint         # eslint
npm run db:push      # push schema
npm run db:seed      # seed baseline data
npm run db:studio    # Drizzle Studio

Build Output

npm run build writes to dist/:

  • dist/client/: browser static assets
  • dist/server/server.js: SSR + server-routes fetch handler

server.mjs wraps that handler with node:http, listens on PORT (default 3000), and serves dist/client/ as static assets. For container deployment, see artifact.toml.

Notes for Agents

The full build instructions live in AGENTS.md. For greenfield work, agents follow this order:

  1. Schema (src/db/schema.ts)
  2. Auth Config (permissions.ts)
  3. Services + Runtime Bootstrap (src/services/**)
  4. Server Routes / API (src/routes/api/**)
  5. Frontend (choose _app, station, review, or a custom layout as needed)
  6. Build (npm run build)

On an existing project, respond directly to the requested change instead of restarting the full build order.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors