# Dolphin Japan — Engineering Guidelines

> Authoritative engineering rules for **humans and AI assistants** working on
> the Dolphin Japan frontend. Read this before writing any code. When this
> document conflicts with anything else in the repository, **this document
> wins**.
>
> Companion docs:
> - [`docs/ARCHITECTURE.md`](../docs/ARCHITECTURE.md) – architectural contract
> - [`docs/API.md`](../docs/API.md) – endpoint contract for the backend team
> - [`docs/DEVELOPMENT.md`](../docs/DEVELOPMENT.md) – local dev workflow
> - [`guidelines/AdminPanelPlan.md`](AdminPanelPlan.md) – admin-panel module spec
> - [`docs/MIGRATION_NOTES.md`](../docs/MIGRATION_NOTES.md) – Vite → Next migration log

---

## 1. Project at a glance

**Dolphin Japan** is a Japanese used-car export e-commerce platform. The
codebase serves **two surfaces** from a single Next.js application:

| Surface         | Route group       | Audience            | Layout                                      |
| --------------- | ----------------- | ------------------- | ------------------------------------------- |
| Public shop     | `src/app/(shop)/` | End customers       | `PublicLayout` (header / footer)            |
| Admin panel     | `src/app/admin/`  | Internal staff      | `AdminLayout` (sidebar / topbar, noindex)   |
| Auth (planned)  | `src/app/(auth)/` | Admin login         | minimal layout (no chrome)                  |

The two surfaces share the same `src/features/*` and `src/shared/*` code, so
**a single set of guidelines applies to both**. Surface-specific rules are
called out in §11 (Public-shop specifics) and §12 (Admin-panel specifics).

### Tech stack (canonical)

| Concern         | Pin                                      |
| --------------- | ---------------------------------------- |
| Framework       | Next.js 15 (App Router)                  |
| Runtime         | React 19 / TypeScript strict             |
| Styling         | Tailwind CSS v4 + CSS variables          |
| UI primitives   | shadcn/ui (preferred), MUI (legacy only) |
| State           | Zustand 5 (per-feature stores)           |
| Forms           | react-hook-form                          |
| Icons           | lucide-react                             |
| Animation       | motion (Framer Motion successor)         |
| Charts          | recharts                                 |
| Data fetching   | Custom `apiFetch` wrapper, no SWR/RQ yet |

> **Do not introduce a new dependency without a clear reason** that could not
> be solved with the libraries above. Adding `axios`, `swr`, `react-query`,
> `redux`, `formik`, `material-ui` to new code requires explicit approval.

---

## 2. Folder structure (Feature-Sliced Design)

```
src/
├── app/                       # Next.js App Router – ROUTES ONLY, no business logic
│   ├── layout.tsx             # Root <html>, fonts, global metadata
│   ├── globals.css            # Aggregates tailwind/theme/admin/fonts
│   ├── (shop)/                # Public route group — wraps PublicLayout
│   ├── (auth)/                # Auth route group (when login lands)
│   └── admin/                 # Admin routes — wraps AdminLayout, noindex
│
├── features/                  # DOMAIN MODULES — the real product code
│   └── {feature}/
│       ├── components/        # Feature UI  (admin/ subfolder if needed)
│       ├── hooks/             # use{Feature}, use{Feature}Form …
│       ├── services/          # {feature}Api.ts — HTTP calls
│       ├── store/             # Zustand slice(s)
│       ├── types/             # {feature}.types.ts
│       ├── data/              # dummy.ts — fallback fixtures
│       └── utils/             # Feature-only helpers
│
└── shared/                    # CROSS-FEATURE REUSABLE CODE
    ├── components/
    │   ├── ui/                # shadcn primitives — editable in-tree
    │   ├── layout/            # PublicLayout, etc.
    │   └── figma/             # Figma helpers (ImageWithFallback)
    ├── hooks/
    ├── lib/                   # api.ts, constants.ts, router-compat.tsx, utils.ts
    └── types/                 # Cross-cutting types (api.types.ts)
```

### Existing features (do not invent new top-level folders without justification)

`vehicles` · `contact` · `orders` · `inquiry` · `payments` · `gallery` ·
`faq` · `pricing` · `admin` · `auth` · `home`

---

## 3. The dependency rule (read this twice)

```
app  →  features  →  shared           ✅
shared  →  features                   ❌ FORBIDDEN
features/{a}  →  features/{b}         ❌ FORBIDDEN
```

If feature A genuinely needs something feature B owns, **lift it to
`src/shared/`**. There is no exception.

`page.tsx` files in `src/app/` are **thin adapters** — they should usually
import a feature component and add metadata. **No data fetching or business
logic in `app/`.**

---

## 4. File and naming conventions

| Concern             | Rule                                                     | Example                                  |
| ------------------- | -------------------------------------------------------- | ---------------------------------------- |
| Component file      | `PascalCase.tsx`                                          | `VehicleCard.tsx`                        |
| Hook file           | `useThing.ts`                                            | `useVehicles.ts`                         |
| Type file           | `kebab-case.types.ts`                                    | `vehicle.types.ts`                       |
| Service file        | `{feature}Api.ts`                                        | `vehiclesApi.ts`                         |
| Store file          | `{feature}Store.ts`                                      | `vehiclesStore.ts`                       |
| Util / data         | `kebab-case.ts`                                          | `format-mileage.ts`, `dummy.ts`          |
| Test file           | `{name}.test.ts(x)`                                      | `vehicleCard.test.tsx`                   |
| Route folder names  | `kebab-case`                                             | `order-custom`, `body-style`             |
| Dynamic segment     | `[param]`                                                | `cars/[id]`                              |
| Boolean variables   | `is*`, `has*`, `should*`                                 | `isLoading`, `hasNextPage`               |
| Event handlers      | `handleX` inside the component, `onX` as prop            | `<Btn onClick={handleSubmit} />`         |

Every directory must have a clear, single responsibility. If a folder accrues
unrelated files, split it.

---

## 5. Imports

```ts
// ✅ Always use the @/ alias for cross-folder imports
import { Button }      from '@/shared/components/ui/button';
import { cn }          from '@/shared/lib/utils';
import { api }         from '@/shared/lib/api';
import type { Vehicle } from '@/features/vehicles/types/vehicle.types';

// ✅ Relative paths only inside the same feature
import { vehiclesApi } from './services/vehiclesApi';
import type { Vehicle } from '../types/vehicle.types';

// ❌ Never use deep relative paths across the tree
import { Button } from '../../../shared/components/ui/button'; // ❌

// ❌ Never import directly between features
// from src/features/orders/components/OrderForm.tsx:
import type { Vehicle } from '@/features/vehicles/types/vehicle.types'; // ❌
```

Order imports as: **(1) external packages → (2) `@/shared/...` → (3)
`@/features/...` → (4) relative**, with a blank line between groups.

---

## 6. Server vs. client components

* **Default to Server Components** in `src/app/`.
* Add `'use client'` **at the top of the file** only when you need:
  hooks (`useState`, `useEffect`, `useReducer`, refs), event handlers,
  browser APIs, or a third-party client library.
* Heavy interactive UIs should be a single `'use client'` boundary at the
  top of the feature component, not sprinkled across many files.
* Page (`page.tsx`) and layout (`layout.tsx`) files in `src/app/` should
  usually stay as Server Components.

If a component is purely presentational and accepts only serializable props,
do **not** mark it `'use client'`.

---

## 7. Data layer — the API contract

All HTTP traffic flows through:

```ts
import { api, withDummyFallback } from '@/shared/lib/api';
```

### 7.1 Service files

Every feature owns a service file at
`src/features/{feature}/services/{feature}Api.ts`. Service functions:

* **Return the unwrapped `data`** — `apiFetch` peels off the
  `{ success, data, message }` envelope automatically.
* **Use `withDummyFallback`** for any read/write that should remain usable
  before the backend ships. Pattern:

  ```ts
  import { api, withDummyFallback } from '@/shared/lib/api';
  import { buildQuery } from '@/shared/lib/utils';
  import type { Paginated, ListQuery } from '@/shared/types/api.types';
  import type { Vehicle } from '../types/vehicle.types';
  import { DUMMY_VEHICLES } from '../data/dummy';

  export const listVehicles = (q: ListQuery = {}) =>
    withDummyFallback(
      () => api.get<Paginated<Vehicle>>(`/vehicles${buildQuery(q)}`),
      () => ({
        items: DUMMY_VEHICLES,
        total: DUMMY_VEHICLES.length,
        page: 1,
        pageSize: DUMMY_VEHICLES.length,
        pageCount: 1,
      }),
    );
  ```

* **Never** call `fetch()` directly in a component or hook — always go
  through the service.

### 7.2 Update [`docs/API.md`](../docs/API.md) on every endpoint touch

When you add, remove, or rename an endpoint:

1. Add/update the row in `docs/API.md`.
2. Mark its status — 🟡 Planned · 🟢 Live · 🔴 Deprecated.
3. Document the request and response shape.

This file is the **single source of truth** the backend team consumes.

### 7.3 Errors

* `apiFetch` throws `ApiClientError` on non-2xx (with `status` and `body`).
* Components should surface errors via the toast system (`sonner`) or a
  dedicated error UI — never via `alert()` or by ignoring rejections.
* Inside a Zustand store action, catch the error and store it in
  `state.error: string | null`.

---

## 8. State management (Zustand)

* One store slice per feature, at
  `src/features/{feature}/store/{feature}Store.ts`.
* Export **selector hooks** so components subscribe to the smallest possible
  slice:

  ```ts
  export const useVehicleList   = () => useVehiclesStore((s) => s.list);
  export const useSelectedVehicle = () => useVehiclesStore((s) => s.selected);
  ```

* Do **not** create a global "master" store.
* Persist only what is genuinely needed across reloads (auth token, theme).
  Use `zustand/middleware`'s `persist` and **partialize** carefully:

  ```ts
  persist(creator, {
    name: 'dj.auth',
    partialize: (s) => ({ token: s.token }),
  })
  ```

* Mutations: `set({ ... })` is fine for shallow updates; reach for `produce`
  from `immer` only if a slice gets genuinely nested (and you must add immer
  to dependencies first).

---

## 9. Components

### 9.1 Definition style

```tsx
type Props = {
  vehicle: Vehicle;
  onSelect?: (id: string) => void;
};

export function VehicleCard({ vehicle, onSelect }: Props) {
  // …
}
```

* **Named exports** for components. Default exports only for
  `app/**/{page,layout,error,not-found}.tsx`.
* Always type props inline as `type Props = { … }`.
* Destructure props in the signature; do not use `props.x` inside the body.

### 9.2 Decomposition

* Keep a component under **~150 lines**. If it grows past that:
  1. Extract subcomponents that own their own slice of UI.
  2. Move logic into a `useThing()` hook in the same feature.
  3. Move pure helpers into the feature's `utils/`.

### 9.3 Lists & keys

* Use a stable, unique `id` field as the React `key`. Never use the array
  index unless the list is genuinely static and order-independent.

### 9.4 Forms

* `react-hook-form` + shadcn `Form` primitives for any form with > 1 field.
* Validate with a single source of truth — typically a `z.object({...})`
  schema in `{feature}/schemas/`. (Add `zod` only when you reach for it; do
  not pre-emptively wire it up.)
* Submit handlers go through the feature's service file.

### 9.5 Accessibility

* Every interactive element must be reachable via keyboard.
* Inputs must have an associated `<label>` (or `aria-label`).
* Buttons must have a discernible name; icon-only buttons need `aria-label`.
* Color contrast ≥ 4.5:1 for body text, ≥ 3:1 for large text.

---

## 10. Styling

### 10.1 Tailwind CSS v4

* Tailwind utilities are the primary styling mechanism.
* Use `cn(...)` from `@/shared/lib/utils` for conditional classes:

  ```tsx
  className={cn('rounded-md border', isActive && 'border-primary')}
  ```

* Custom design tokens live in `src/styles/theme.css` as CSS variables.
  Reference them through Tailwind utilities, e.g. `bg-primary`,
  `text-foreground`, `border-border`.

### 10.2 Brand colors

```
Primary navy   #00275c
Accent blue    #0589d9
Deep navy      #001a3e
Footer dark    text-white/footer surfaces use these via inline literals or theme.css.
```

Use inline `bg-[#00275c]` or `text-[#0589d9]` only when the design calls
for the brand color directly. Otherwise stick with semantic Tailwind
utilities (`bg-primary`, `text-accent-foreground`).

### 10.3 Shadcn vs MUI

* **New code must use shadcn primitives** from `src/shared/components/ui/`.
* MUI is permitted in admin pages that already use it; do not introduce
  MUI to new files. Migrate ad-hoc when convenient.

### 10.4 Fonts

Inter is loaded via `next/font/google` in `src/app/layout.tsx` and exposed
as the CSS variable `--font-inter`. **Do not** add `<link>` tags or
`@import url(...)` for web fonts — `next/font` handles preloading,
self-hosting, and CLS prevention.

### 10.5 Money formatting

* Display via `formatJPY(amount)` or `formatUSD(amount)` from
  `@/shared/lib/utils`.
* Never inline `Intl.NumberFormat` in a component.
* Wire-format JPY values are **integer yen**; USD uses major units (dollars).

---

## 11. Public-shop specifics (`app/(shop)`)

* Pages should be **server-rendered** when possible for SEO. Server-fetch
  data through the feature's service file in the page itself, then pass
  to a client component:

  ```tsx
  // app/(shop)/inventory/page.tsx — server component
  import { listVehicles } from '@/features/vehicles/services/vehiclesApi';
  import { InventoryClient } from '@/features/vehicles/components/InventoryClient';

  export const dynamic = 'force-dynamic'; // or 'auto' / revalidate
  export default async function InventoryPage() {
    const initial = await listVehicles();
    return <InventoryClient initial={initial} />;
  }
  ```

* All public pages must export `metadata` (title, description, OG image).
* Use `next/image` for any new image. Add CDN domains to `next.config.ts`
  before referencing them.
* All visitor-facing strings must be plain English in code today; mark
  user-visible copy that will need i18n with a comment so we can sweep it
  into a future `next-intl` migration.

---

## 12. Admin-panel specifics (`app/admin`)

* Every admin page lives behind `app/admin/layout.tsx`, which wraps
  `AdminLayout` and adds `metadata.robots = { index: false, follow: false }`.
* Admin route folders use `kebab-case`: `body-style`, `price-calculator`,
  `inventory-inquiry`.
* Admin UI for a feature lives at
  `src/features/{feature}/components/admin/...`. The exception is
  cross-cutting admin chrome which lives in `src/features/admin/`.
* Use the shared **DataTable** primitive (when it lands at
  `src/shared/components/ui/DataTable.tsx`) for any list view: pass
  `columns`, `data`, `searchFields`. Until then, follow the pattern in
  existing admin list pages — search input + sortable headers + footer
  pagination.
* Modals: use `AdminModal` for create/edit and `ConfirmDialog` for
  destructive confirmations. Both live in
  `src/features/admin/components/`.
* All admin forms must:
  * Show a loading spinner while submitting.
  * Disable the submit button while pending.
  * Surface API errors via `toast.error(...)`.
  * Close the modal and refresh the parent list on success.
* See [`AdminPanelPlan.md`](AdminPanelPlan.md) for the per-module field
  contract (columns, form fields, permissions).

---

## 13. Routing & navigation

* Use the App Router file-system routes. Do not add a custom router or a
  `pages/` directory.
* New components should import navigation primitives directly from
  `next/link` and `next/navigation`. **Do not import from
  `@/shared/lib/router-compat`** in new code — that shim only exists to
  keep migrated components compiling.
* Internal navigation: `<Link href="/...">` (not `<a>`).
* Imperative navigation: `const router = useRouter(); router.push('/...');`.

---

## 14. Environment & configuration

* `.env.local` is gitignored. `.env.local.example` is the canonical list
  of supported variables.
* All environment access goes through `src/shared/lib/constants.ts`.
  Components/hooks should never read `process.env` directly.
* Secret values (API keys, signing secrets) must be **server-only** — name
  them without the `NEXT_PUBLIC_` prefix and access them only from server
  components, route handlers, or server actions.

---

## 15. Testing & verification (before every PR)

```bash
npm run typecheck   # must pass with zero errors
npm run lint        # fix all warnings introduced by your change
npm run build       # must succeed
```

If a change touches public pages, manually verify:

* The page renders at the dev server without console errors.
* The page renders correctly on a 360-px-wide viewport.
* Lighthouse score does not regress meaningfully (Performance ≥ 80,
  Accessibility ≥ 95).

---

## 16. Git workflow

* Branch off `main`: `feat/short-description`, `fix/...`, `chore/...`,
  `docs/...`, `refactor/...`.
* Commit message format: **conventional commits**.
  ```
  feat(vehicles): add make/model facet filter
  fix(orders): close modal on submit success
  docs(api): document /admin/payments endpoints
  ```
* Each commit should typecheck on its own. Squash WIP fixups before
  pushing.
* PR description must include: **what** changed, **why**, **screenshots**
  for any visible change, and **any new env vars or migration steps**.

---

## 17. AI-assistant playbook

When you (the AI assistant) are asked to implement a feature, follow this
sequence:

1. **Locate the feature** under `src/features/`. If it does not exist,
   create the folder with the layout from §2 — but only the subfolders
   you actually need.
2. **Define types first** in `{feature}.types.ts`.
3. **Add or update dummy data** in `data/dummy.ts`.
4. **Write the service file** using `withDummyFallback` for every public
   call (§7.1). Wire request/response types to the types from step 2.
5. **Update `docs/API.md`** with the endpoints used (§7.2). Mark status.
6. **Build the UI** under `components/`, decomposing per §9.2. Add
   `'use client'` only where required (§6).
7. **Wire it to a route** in `src/app/...` with a thin `page.tsx`. Set
   `metadata` for shop pages; set `metadata.robots` for admin pages.
8. **Verify** — run `npm run typecheck` and `npm run lint`. Fix issues
   you introduced; do not "fix" unrelated lint warnings.
9. **Update docs** when conventions or contracts change. Never duplicate
   information that lives elsewhere — link to it.

### Hard rules for AI assistants

* **Do not** introduce new top-level dependencies without explicitly
  flagging it to the user and waiting for approval.
* **Do not** rewrite files you did not need to touch. Surgical edits only.
* **Do not** create a markdown report after a code change unless the user
  asked. Confirm the change in chat instead.
* **Do not** invent endpoints, env vars, or feature folders that are not
  already defined here or in `docs/API.md`.
* **Do** preserve existing comments and code style of the file you are
  editing.
* **Do** use the `@/` alias and named exports.
* **Do** add `'use client'` at the top of any new client component file.
* **Do** wrap public service calls in `withDummyFallback` while the
  backend is in flight.
* **Do** treat `docs/API.md` as the contract — update it whenever an
  endpoint surface changes.

---

## 18. Quick-reference checklist

Before committing, verify each:

- [ ] No cross-feature imports.
- [ ] No `process.env` reads outside `shared/lib/constants.ts`.
- [ ] No `react-router` imports in new code.
- [ ] All new client components start with `'use client'`.
- [ ] All HTTP calls go through `api`/`apiFetch`, never raw `fetch`.
- [ ] Public service calls use `withDummyFallback`.
- [ ] Money formatted with `formatJPY` / `formatUSD`.
- [ ] Tailwind classes use `cn()` for conditionals.
- [ ] Shop pages export `metadata`; admin pages set `metadata.robots`.
- [ ] `docs/API.md` reflects any endpoint touched.
- [ ] `npm run typecheck` passes.
