---
name: frontend
description: Use this skill for Vue frontend work in this repository, especially when creating or refactoring views, components, composables, config, assets, or UI structure in resource/frontend.
---

# Frontend Skill

Use this skill for all Vue frontend work in this repository.

## Core Principles

- Follow the repository frontend structure strictly.
- Prefer explicit, predictable file placement and naming over ad hoc organization.
- Keep views independent unless there is a very small and focused abstraction that clearly reduces duplication.
- Use shadcn primitives from `src/components/ui/` instead of raw HTML controls.
- Raw HTML is allowed for layout structure only.
- Use Pinia only for global app state.
- Default to `@tanstack/vue-query` for the data layer.
- For backend API access, strictly use the Orval-generated queries, mutations, and models under `src/api/generated/`.
- Prefer generated models over handwritten API types. Do not introduce or reuse `@/types` for backend response/request shapes when an Orval-generated type exists.
- For frontend navigation, use `useNavigation` from `src/composables/useNavigation.ts` with `ROUTES` constants instead of raw `useRouter().push`/`replace` path strings so navigation stays typed and centralized.
- Put all user-facing text in locale files.
- Default to English for now, but structure all new text so localization is easy later.

## UI Rules

- Do not build buttons, inputs, badges, dialogs, selects, tables, tooltips, or similar UI controls directly with raw HTML if an existing shadcn primitive exists.
- Prefer components from `src/components/ui/` first.
- If a needed primitive does not exist yet, add it under `src/components/ui/` in the same style as the existing shadcn components rather than creating one-off UI markup in a feature view.
- Raw HTML should be used for layouting only:
  - page sections
  - wrappers
  - flex/grid containers
  - semantic structure such as `section`, `header`, `main`, `nav`, `aside`
- Tailwind should support layout and spacing, but reusable UI appearance should come from shared shadcn primitives or feature components.

## Source Structure

Frontend source must follow this structure:

```text
src/
  components/
    *.component.ts
  components/ui/
    # shadcn components
  composables/
    *.composable.ts
  views/
    [name-view]/
      [Name].view.vue
      components/
        [NameViewComponent].component.vue
      composables/
        [nameViewComposable].composable.ts
  constants/
    *.constants.ts
  helpers/
    *.helpers.ts
  config/
    *.config.ts
    i18n.config.ts
  assets/
    locales/
      *.locale.json
    images/
    icons/
```

## Folder Rules

### `src/components`

- Use for global reusable frontend components.
- Files in this folder should follow the `.component.ts` convention when they are TypeScript component entry modules or helpers for a component.
- Vue SFC reusable components should also be treated as shared components and named consistently with the component convention used by the team.
- Do not place view-specific components here if they are only used by one view.

### `src/components/ui`

- This folder contains shared shadcn primitives.
- Treat it as the default source for base UI building blocks.
- Prefer extending this layer over duplicating low-level UI patterns inside views.
- Keep these components generic and reusable.

### `src/composables`

- Use for global composables shared across multiple views or domains.
- Files must use the `.composable.ts` suffix.
- Global composables should not contain highly view-specific state or assumptions.
- Query composables that wrap `@tanstack/vue-query` are preferred for server state and remote data fetching.
- When backend endpoints are covered by Orval, use the generated query/mutation hooks directly or build thin wrappers around them. Do not hand-roll parallel API typings.
- Do not create generic `use[Domain]Api` or `use[Whatever]Api` adapter composables that merely wrap Orval-generated hooks or turn them into imperative fetch helpers.
- If several generated hooks must be coordinated, do that coordination directly in the consuming view or in a tightly scoped feature composable that owns the behavior, rather than introducing a shared API facade.

### `src/stores`

- Pinia stores are for global app state only.
- Do not use Pinia as the default place for feature state, page state, form state, editor state, or server state.
- If state is local to a page or feature, keep it inside the view or inside a focused composable.
- If state comes from APIs or backend resources, prefer `@tanstack/vue-query` instead of storing fetch lifecycle state in Pinia.

### `src/views`

- Each view should live in its own folder.
- Each view entry file should be named `[Name].view.vue`.
- Each view folder may contain:
  - `components/` for view-local UI pieces
  - `composables/` for view-local composables
- View-local components should be named `[NameView-Component].component.vue`.
- View-local composables should be named `[nameViewComposable].composable.ts`.
- Do not leak one view's local components into another view. If a component becomes cross-view, promote it to `src/components`.

### `src/constants`

- Constant modules must use the `.constants.ts` suffix.
- Keep only constant values and related static mappings here.

### `src/helpers`

- Helper modules must use the `.helpers.ts` suffix.
- Use helpers for pure utility logic, formatting helpers, mapping helpers, and transformation helpers that do not need Vue reactivity.

### `src/config`

- Config files must use the `.config.ts` suffix.
- `i18n.config.ts` is the reference example.
- Keep environment-independent frontend configuration here.

### `src/assets`

- `assets/locales/` must contain `.locale.json` files.
- `assets/images/` stores images.
- `assets/icons/` stores icons.
- User-facing strings should be defined in locale files rather than hardcoded in components.

### `src/i18n`

- Treat localization as part of the default frontend architecture.
- Even if English is the only active language today, all new user-facing copy should be added through locale keys first.
- Do not hardcode visible UI strings in views, reusable components, dialogs, buttons, labels, empty states, or notifications unless there is a very short-lived reason.
- Write locale content in English by default for now.

## View Composition Rules

- A view file should primarily compose the page, wire data flow, and connect actions.
- Large chunks of repeated or complex UI should be extracted into view-local components under that view's `components/`.
- Repeated view-specific logic should be extracted into small view-local composables under that view's `composables/`.
- Do not create broad shared state composables for multiple views unless the abstraction is very small, focused, and action-oriented.
- Shared behavior is acceptable.
- Shared view state is discouraged unless there is a very strong reason.
- Prefer `@tanstack/vue-query` composables for loading, caching, invalidation, background refresh, and mutation flows tied to backend data.
- For API contracts, Orval-generated types are the source of truth.
- Keep client-only UI state local unless it is truly global application state.

## Naming Rules

- Use PascalCase for Vue component file names that represent components or views.
- Use camelCase for composables, helpers, and similar TypeScript modules.
- Keep suffixes explicit:
  - `.view.vue`
  - `.component.vue`
  - `.component.ts`
  - `.composable.ts`
  - `.constants.ts`
  - `.helpers.ts`
  - `.config.ts`
  - `.locale.json`

## What To Avoid

- Do not place feature-specific components directly in shared folders.
- Do not create raw `<button>`, `<input>`, `<select>`, or similar controls when a shadcn primitive already exists.
- Do not mix unrelated concerns in one file just because it is convenient.
- Do not use global composables to share entire view state by default.
- Do not use Pinia for local page state or server-state caching.
- Do not hand-roll API loading state in stores when `@tanstack/vue-query` should own it.
- Do not use handwritten `@/types` request/response models for endpoints already represented in `src/api/generated/`.
- Do not create duplicate frontend API contract types when Orval already generates them.
- Do not introduce shared imperative API facade composables on top of Orval when the generated hooks can be used directly in the view or feature composable.
- Do not hardcode user-visible text in components when it belongs in locale files.
- Do not create naming exceptions unless there is an established repository reason.

## Preferred Decision Order

When adding frontend code, decide in this order:

1. Is this a base UI primitive? Put it in `src/components/ui/`.
2. Is this a shared reusable component? Put it in `src/components/`.
3. Is this only for one view? Put it under that view's `components/` or `composables/`.
4. Is this remote or server-backed data? Use `@tanstack/vue-query`, usually via a composable.
5. Is this shared reactive logic across multiple features but not global app state? Put it in `src/composables/`.
6. Is this truly global app state? Use Pinia in `src/stores/`.
7. Is this static configuration, constants, or pure helper logic? Use `config/`, `constants/`, or `helpers/`.

## Expected Outcome

Frontend changes should result in:

- clear file ownership
- predictable placement
- explicit naming
- shadcn-first UI composition
- Pinia limited to global app state
- `@tanstack/vue-query` as the default data layer
- English-first locale-driven text
- raw HTML reserved mostly for layout structure
- smaller, more maintainable Vue views
