Imported from Gymnasium-Riedberg/abi-app (
AGENTS.md). Install upstream withnpx skills add Gymnasium-Riedberg/abi-app. Copyright stays with the author.
AGENTS.md
Abi App (GRB AbiApp) — cross-platform app for Gymnasium Riedberg and partner schools. Vue 3 + TypeScript frontend, Tauri 2 Rust backend, Shadcn-Vue UI. Targets Web, Desktop (Linux/macOS/Windows), Android, and iOS.
Backend data layer: self-hosted Supabase (Auth, Postgres, Storage, Realtime). The app talks to Supabase via @supabase/supabase-js — configure the instance URL and publishable key through environment variables.
Tech stack
| Layer | Technology |
|---|---|
| UI framework | Vue 3 (<script setup> SFCs) |
| Language | TypeScript 5.6 (strict) |
| Build | Vite 6 |
| Desktop / mobile shell | Tauri 2 |
| Native bridge | Rust (edition 2021) |
| Backend / data | Self-hosted Supabase (@supabase/supabase-js) |
| Routing | Vue Router 5 (file-based auto-routes from src/pages) |
| State Management | Pinia |
| UI components | Shadcn-Vue 2 (style: reka-nova) |
| Primitives | Reka UI, Vaul Vue |
| Styling | Tailwind CSS 4 (@tailwindcss/vite), tw-animate-css |
| Icons | Tabler (@tabler/icons-vue), Lucide Vue |
| Forms | VeeValidate + Zod (@vee-validate/zod), vue-input-otp |
| Tables | TanStack Vue Table |
| Toasts | vue-sonner |
| Animation | GSAP, VueUse Motion |
| 3D Graphics | TresJS (Cientos, Core, Post-processing), Three.js |
| Data Visualization | Unovis Vue |
| HTTP Client | Ky |
| Date/Time | @internationalized/date |
| Utilities | @vueuse/core, class-variance-authority, clsx, tailwind-merge |
| Carousel | Embla Carousel Vue |
| Querying | TanStack Vue Query |
| Testing | Vitest + @vue/test-utils |
| Package manager | pnpm (required — Tauri config uses pnpm) |
App identifier: de.gymnasium-riedberg.abi-app
Commands
# Install dependencies
pnpm install
# Web dev server (port 1420, strict)
pnpm dev
# Type-check + production web build → dist/
pnpm build
# Preview production web build
pnpm preview
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Desktop dev (starts Vite + Tauri window)
pnpm tauri dev
# Desktop production build
pnpm tauri build
# Android (requires SDK; init once with `pnpm tauri android init`)
pnpm tauri android dev
pnpm tauri android build
# iOS (requires Xcode; init once with `pnpm tauri ios init`)
pnpm tauri ios dev
pnpm tauri ios build
Rust native code: src-tauri/ — use cargo check / cargo clippy when changing Rust code.
pnpm build runs vue-tsc --noEmit as the type-check gate. pnpm test runs Vitest.
Environment variables
Copy .env.example to .env and set values for your self-hosted Supabase instance:
| Variable | Purpose |
|---|---|
VITE_SUPABASE_URL |
Supabase API URL (e.g. https://supabase.example.com) |
VITE_SUPABASE_PUBLISHABLE_KEY |
Supabase publishable/public key |
Never commit .env or credentials. The Supabase client lives in src/lib/supabase.ts.
Project structure
abi-app/
├── src/ # Vue frontend
│ ├── main.ts # App entry — mounts Vue, router, global CSS
│ ├── App.vue # Root shell (`RouterView`)
│ ├── vite-env.d.ts # Vite env type declarations
│ ├── route-map.d.ts # Auto-generated route type map (do not edit)
│ ├── pages/ # File-based routes (auto-generated by Vue Router)
│ │ ├── (app)/ # Route group — authenticated app shell
│ │ ├── (auth)/ # Route group — unauthenticated auth flows
│ │ ├── admin/ # Admin-only pages (guarded in router)
│ │ └── [...path].vue # Catch-all 404
│ ├── layouts/ # Layout wrappers (via vite-plugin-vue-layouts)
│ │ ├── default.vue # Fallback layout
│ │ ├── AppLayout.vue # Main authenticated app layout
│ │ ├── AuthLayout.vue # Unauthenticated / auth flow layout
│ │ └── AdminLayout.vue # Admin section layout
│ ├── router/
│ │ └── index.ts # Router instance — auth guard + admin guard
│ ├── api/ # Thin API call functions (Supabase queries, no business logic)
│ ├── services/ # Business logic / orchestration layer
│ ├── stores/ # Pinia stores (global reactive state)
│ ├── composables/ # Shared Vue composables (grouped by domain)
│ ├── constants/ # App-wide constant values
│ ├── utils/ # Pure utility/helper functions (no Vue dependencies)
│ ├── components/
│ │ ├── ui/ # Shadcn-Vue components (already installed — do not re-add)
│ │ ├── common/ # Shared structural components
│ │ │ ├── global/ # Components used across all platforms
│ │ │ ├── desktop/ # Desktop-specific variants
│ │ │ └── mobile/ # Mobile-specific variants
│ │ ├── context/ # Context-provider wrapper components
│ │ ├── dialogs/ # Modal / dialog components
│ │ ├── forms/ # Reusable form components
│ │ ├── providers/ # Vue provider components (DI pattern)
│ │ └── widgets/ # Self-contained UI widgets
│ ├── lib/
│ │ ├── utils.ts # `cn()` helper (clsx + tailwind-merge)
│ │ ├── supabase.ts # Supabase client singleton
│ │ └── platform.ts # Cross-platform detection helpers (isTauri, isWeb, etc.)
│ ├── assets/
│ │ ├── index.css # Tailwind + Shadcn theme variables (do not duplicate)
│ │ ├── images/ # Static image assets
│ │ └── data/
│ │ └── locales/ # i18n JSON translation files
│ │ ├── de/ # German translations
│ │ └── en/ # English translations
│ └── __mocks__/ # Vitest module mocks (auto-routes, layouts, etc.)
├── src-tauri/ # Tauri / Rust native layer
│ ├── src/
│ │ ├── main.rs # Desktop entry
│ │ └── lib.rs # Shared entry (mobile + desktop), Tauri commands
│ ├── capabilities/ # Tauri 2 permission capabilities
│ └── tauri.conf.json # Tauri app config
├── infra/ # Infrastructure — DB migrations, self-hosted Supabase config
│ ├── migrations/ # SQL migration files (apply in order)
│ └── supabase/ # Self-hosted Supabase Docker setup
├── components.json # Shadcn-Vue CLI config (aliases, style, theme)
├── vite.config.ts # Vite + Vue Router plugin + Tailwind
└── index.html # HTML shell
Frontend conventions
Vue
- Use
<script setup lang="ts">for all components. - Prefer Composition API; no Options API unless matching existing code.
- Pages (route components) live in
src/pages/— routes are generated automatically.(app)/— authenticated routes (wrapped in AppLayout)(auth)/— unauthenticated routes (wrapped in AuthLayout)admin/— admin-only routes (guarded via router + RLS check)
- Layouts live in
src/layouts/— assign via<route>block meta ordefinePage. - Path alias
@/maps tosrc/(configured invite.config.tsandtsconfig.json).
Code organization
| Directory | Purpose | When to use |
|---|---|---|
api/ |
Supabase query functions | Direct database/auth calls — thin wrapper over Supabase client |
services/ |
Business logic | Orchestrate API calls, transform data, implement domain logic |
stores/ |
Pinia stores | Global reactive state (user session, app-wide settings, etc.) |
composables/ |
Vue composables | Reusable reactive logic (DOM, lifecycle, refs) — group by domain |
constants/ |
App constants | Enum-like values, config constants, magic numbers |
utils/ |
Pure functions | Helpers with no Vue/Tauri dependencies (format, parse, etc.) |
lib/ |
Core utilities | Framework-level abstractions (platform detection, client singletons) |
components/common/ |
Shared components | global/ (all platforms), desktop/, mobile/ platform-specific |
Prefer this layering:
Page/Component → Composable → Service → API → Supabase
Platform detection
Use @/lib/platform for cross-platform logic:
import { isTauri, isWeb, isTauriMobile, isMobileOS } from '@/lib/platform'
isTauri()— running in Tauri shell (desktop or mobile)isWeb()— running in browser (no Tauri)isTauriMobile()— Tauri on Android/iOSisMobileOS()— any mobile OS (Tauri or web browser)
Guard Tauri API calls: if (isTauri()) { /* invoke Tauri command */ }
Routing (file-based auto-routes)
Routes are generated from src/pages/ by the Vue Router Vite plugin — no manual route tables.
| File | Route |
|---|---|
src/pages/index.vue |
/ |
src/pages/about.vue |
/about |
src/pages/users/[id].vue |
/users/:id |
src/pages/[...path].vue |
catch-all (404) |
Router setup in src/router/index.ts:
import { createRouter, createWebHistory } from "vue-router";
import { routes } from "vue-router/auto-routes";
import { setupLayouts } from "virtual:generated-layouts";
The router applies two guards automatically:
- Auth guard — redirects unauthenticated users to
/login; redirects authenticated users away from auth routes - Admin guard — checks
profiles.is_adminvia Supabase before allowing access to/admin/*
Assign a layout in a page:
<route lang="yaml">
meta:
layout: AppLayout
</route>
Default layout: src/layouts/default.vue. Run pnpm dev after adding pages so src/route-map.d.ts stays up to date.
TypeScript
strictmode is enabled — no implicitany, handle unused locals/parameters.- Run
pnpm build(orvue-tsc --noEmit) after substantive TS changes.
Styling
- Use Tailwind utility classes; theme tokens come from CSS variables in
src/assets/index.css. - Use semantic tokens (
bg-background,text-foreground,border-border, etc.) — not raw hex colors. - Dark mode: class-based via
.darkancestor (@custom-variant darkin index.css). - Font: Noto Sans (loaded in index.css).
- Merge conditional classes with
cn()from@/lib/utils. - Platform-adaptive layouts: use
common/desktop/andcommon/mobile/component variants where behavior meaningfully differs between form factors.
Shadcn-Vue
Configured in components.json. All UI primitives are already installed in src/components/ui/ — do not re-add existing components.
| Alias | Path |
|---|---|
@/components |
components |
@/components/ui |
ui |
@/lib/utils |
utils |
@/lib |
lib |
@/composables |
composables |
- Import from barrel:
import { Button } from '@/components/ui/button' - Components use Reka UI primitives +
cn()for class merging. - Form fields:
@/components/ui/formwith VeeValidate + Zod schemas. - Notifications:
vue-sonnervia@/components/ui/sonner.
Only add new Shadcn components when genuinely needed: pnpm dlx shadcn-vue@latest add <name>
i18n
Translation strings live in src/assets/data/locales/ split by locale (de/, en/). Each locale contains JSON files organized by feature area. Use the useI18n composable from @/composables/useI18n to access translations.
Supabase
- Use the shared client from
@/lib/supabase— do not create multiple clients. - Auth, database queries, storage, and realtime go through Supabase against the self-hosted instance.
- Row Level Security (RLS) policies are enforced server-side — do not rely on client-side checks alone.
- Env vars are read at build time (
import.meta.env.VITE_*).
Testing (Vitest)
- Test files:
src/**/__tests__/*.spec.tsorsrc/**/*.spec.ts - Run:
pnpm testorpnpm test:watch - Use
@vue/test-utilsfor component tests. - Mock auto-generated modules in tests via
src/__mocks__/(e.g.vue-router/auto-routes,virtual:generated-layouts). - Place unit tests next to the code they cover (
src/lib/__tests__/, etc.).
Tauri / Rust conventions
- Tauri commands live in
src-tauri/src/lib.rs; register ininvoke_handler. - Mobile entry point:
#[cfg_attr(mobile, tauri::mobile_entry_point)]onrun()inlib.rs. - Plugins:
tauri-plugin-openeris registered — add new plugins in bothCargo.tomlandlib.rs. - Permissions: Tauri 2 uses capability files in
src-tauri/capabilities/. Add permissions there before using new Tauri APIs. - Frontend ↔ Rust:
@tauri-apps/apiv2 (invoke, etc.). Guard Tauri-only calls when running as pure web (checkwindow.__TAURI__or use@tauri-apps/api/corehelpers).
Vite dev server for Tauri:
- Port 1420 (strict), HMR on 1421 when
TAURI_DEV_HOSTis set. - Do not change the port without updating
tauri.conf.jsondevUrl.
Multi-platform notes
| Target | How to run | Notes |
|---|---|---|
| Web | pnpm dev / pnpm build |
No Tauri APIs unless guarded |
| Desktop | pnpm tauri dev / build |
Default Tauri window config in tauri.conf.json |
| Android | pnpm tauri android dev |
Requires Android SDK; init with tauri android init |
| iOS | pnpm tauri ios dev |
Requires macOS + Xcode; init with tauri ios init |
- Test responsive layouts — mobile targets exist alongside desktop window defaults (800×600).
- Avoid desktop-only UX (hover-only interactions, tiny click targets) on shared views.
src-tauri/tauri.conf.jsonbundle.targetsis"all"— builds for all configured platforms.
Boundaries — do not
- Commit secrets,
.envfiles, or credentials. - Edit
node_modules/,dist/, or generated Taurisrc-tauri/gen/output. - Change
pnpmto npm/yarn — TauribeforeDevCommand/beforeBuildCommandusepnpm. - Duplicate theme variables outside
src/assets/index.css. - Hand-edit Shadcn-Vue components unless fixing a bug — prefer wrapping in parent components.
- Remove
windows_subsystemattribute insrc-tauri/src/main.rs. - Force-push to
main/master.
Definition of done
- Type-check passes:
pnpm build(orvue-tsc --noEmit) - Tests pass:
pnpm test - App runs on the intended target (
pnpm dev,pnpm tauri dev, or mobile command) - New UI uses Shadcn-Vue components and theme tokens — no ad-hoc CSS unless necessary
- Tauri API usage is permission-gated in capabilities and works on web fallback where applicable
- Supabase access uses
@/lib/supabasewith env-configured self-hosted instance - New pages added under
src/pages/(not manual route definitions) - No secrets or generated artifacts committed
IDE setup
Recommended extensions (see .vscode/extensions.json):
- Vue - Official (Volar)
- Tauri
- rust-analyzer
