Skip to content

Commit e0b401c

Browse files
authored
Merge pull request #4 from codee-sh/release/v1.1.0
Release: v1.1.0
2 parents 4a6dfac + fcdb686 commit e0b401c

78 files changed

Lines changed: 6157 additions & 971 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
---
2+
name: frontend-build-components
3+
description: Step-by-step guide for building front-end React components and pages in this Next.js + Tailwind app. Use when adding a UI primitive, a shared component, a feature component, or a frontend page (anything under src/components or src/app/[locale]/(frontend)). Do NOT use for Payload admin views - use payload-build-modules instead.
4+
---
5+
6+
# Front-end — building components and pages
7+
8+
Front-end code lives under `src/components/` (React components) and `src/app/[locale]/(frontend)/` (routed pages). Styling is Tailwind with design tokens; every reusable class string is centralised in `src/lib/class-names.ts`. Text goes through `next-intl`.
9+
10+
The single most important decision is **where a component belongs**. Get that right and the rest follows.
11+
12+
---
13+
14+
## Where does it go? (decision tree)
15+
16+
```
17+
Is it a generic primitive with ZERO domain knowledge
18+
(Button, Input, Alert, Field, Logo, PageHeader)?
19+
└── yes → src/components/ui/{name}/
20+
21+
Is it app-specific but shared across unrelated features
22+
(LogoutButton)?
23+
└── yes → src/components/common/{name}/
24+
25+
Is it tied to one domain feature (workout, plan, ...)?
26+
└── yes → src/components/{domain}/{feature}/
27+
28+
Is it a routed screen?
29+
└── yes → src/app/[locale]/(frontend)/{route}/page.tsx
30+
```
31+
32+
Rule of thumb: if it imports a domain type (`TExercise`, `SetLog`, `MetricField`) or a domain helper (`@/lib/metrics`), it does **not** belong in `ui/`.
33+
34+
---
35+
36+
## Structure per location
37+
38+
### `ui/` and `common/` — one folder + barrel per component
39+
40+
```
41+
src/components/ui/{name}/
42+
├── {name}.tsx # the component
43+
└── index.ts # export { Name } from './{name}'
44+
```
45+
46+
Real examples: `ui/button`, `ui/input` (exports `Input` and `Select`), `ui/field`, `ui/page-header`, `common/logout-button`.
47+
48+
### `{domain}/{feature}/` — main file + `components/` + hooks
49+
50+
```
51+
src/components/{domain}/{feature}/
52+
├── {feature}.tsx # main component (composer / orchestrator)
53+
├── index.ts # export { Feature } from './{feature}'
54+
├── hooks/ # (optional) feature-level hooks, one file each
55+
│ └── use-{feature}-{type}.ts
56+
└── components/ # every sub-component gets its own folder + barrel
57+
└── {sub-component}/
58+
├── {sub-component}.tsx
59+
└── index.ts
60+
```
61+
62+
Real example — `workout/exercise-card/`:
63+
```
64+
exercise-card.tsx # composer: header + meta + list + actions
65+
index.ts
66+
components/
67+
├── exercise-header/
68+
│ ├── exercise-header.tsx
69+
│ └── index.ts
70+
├── add-set-actions/
71+
│ ├── add-set-actions.tsx
72+
│ └── index.ts
73+
├── series-list/
74+
│ ├── series-list.tsx
75+
│ └── index.ts
76+
└── meta-line/
77+
├── meta-line.tsx
78+
└── index.ts
79+
```
80+
81+
Real example — `workout/workout-plans/`:
82+
```
83+
workout-plans.tsx
84+
index.ts
85+
hooks/use-workout-selection.ts
86+
components/
87+
├── active-context-banner/
88+
│ ├── active-context-banner.tsx
89+
│ └── index.ts
90+
└── workout-pickers/ # one folder may export a few closely-related components
91+
├── workout-pickers.tsx # MicrocyclePicker, WorkoutPicker
92+
└── index.ts
93+
```
94+
95+
**Rule:** every React component gets its own kebab-case folder with an `index.ts` barrel - in `ui/`, in `common/`, and inside a feature's `components/`. The only `.tsx` allowed directly at a feature root is the `{feature}.tsx` entry itself. No flat sub-component files. (Mirrors `payload-build-modules`: one folder per component.)
96+
97+
Sub-component-specific hooks go in `components/{sub-component}/hooks/`; hooks for the feature entry stay in `{feature}/hooks/`.
98+
99+
---
100+
101+
## Steps
102+
103+
### 1. Pick the location (decision tree above) and create the folder
104+
105+
`ui`/`common`: `{name}/{name}.tsx` + `index.ts`. Feature: add `components/{name}/{name}.tsx` + `index.ts` in the existing feature folder, or a new `{feature}/` folder with `{feature}.tsx` + `index.ts`.
106+
107+
### 2. Style via `class-names.ts`, not inline strings
108+
109+
Reusable class strings live in `src/lib/class-names.ts`. Compose with `joinClasses`. Prefer a typed `variant` prop over passing class strings around.
110+
111+
```tsx
112+
// src/components/ui/button/button.tsx
113+
import { joinClasses, primaryButtonClass, secondaryButtonClass } from '@/lib/class-names'
114+
115+
type Variant = 'primary' | 'secondary'
116+
const variantClass: Record<Variant, string> = {
117+
primary: primaryButtonClass,
118+
secondary: secondaryButtonClass,
119+
}
120+
121+
export function Button({ className, variant = 'primary', ...props }: ButtonProps) {
122+
return <button className={joinClasses(variantClass[variant], className)} {...props} />
123+
}
124+
```
125+
126+
When a class string is repeated in 2+ places, promote it to `class-names.ts`. When a layout/markup block repeats, extract a component instead.
127+
128+
### 3. Server page → loader → client component
129+
130+
Pages are **server components**. They fetch through a loader in `src/loaders/`, then pass plain data to client components. Never query inside the JSX.
131+
132+
```tsx
133+
// src/app/[locale]/(frontend)/page.tsx (server)
134+
import { getTranslations } from 'next-intl/server'
135+
import { loadTrainingPlans } from '@/loaders/training-plan-loader'
136+
import { PageContainer } from '@/components/ui/page-container'
137+
import { PageHeader } from '@/components/ui/page-header'
138+
import { WorkoutPlans } from '@/components/workout/workout-plans'
139+
140+
export default async function HomePage() {
141+
const t = await getTranslations('home')
142+
const result = await loadTrainingPlans()
143+
if (!result.user) redirect('/login')
144+
145+
return (
146+
<PageContainer>
147+
<PageHeader title={t('greeting', { name: result.user.name ?? '' })} right={<LogoutButton />} />
148+
{result.plans.length > 0 ? (
149+
<WorkoutPlans plans={result.plans} />
150+
) : (
151+
<div className="py-10 text-center text-sm text-ui-fg-muted">{t('noPlans')}</div>
152+
)}
153+
</PageContainer>
154+
)
155+
}
156+
```
157+
158+
Loaders are plain async functions returning a typed result (e.g. a discriminated union for auth state). Reuse `PageContainer` / `PageHeader` for every page shell.
159+
160+
### 4. Client components own UI state; mutations go through `sdk`
161+
162+
```tsx
163+
'use client'
164+
import { useState } from 'react'
165+
```
166+
167+
A component is `'use client'` only when it needs state, effects, or event handlers. Data mutations use `@/lib/sdk`. When a component has complex state or 2+ mutations, extract a hook (step 5).
168+
169+
### 5. Extract a hook when state/mutations get heavy
170+
171+
Place it in `hooks/` inside the feature folder: `components/{domain}/{feature}/hooks/use-{feature}-{type}.ts`.
172+
173+
```ts
174+
// workout/workout-tracker/hooks/use-workout-session.ts
175+
'use client'
176+
export function useWorkoutSession(workout: TWorkout, opts: { readOnly?: boolean; showResults?: boolean }) {
177+
// session state, derived selectors, addSet/updateSet/deleteSet
178+
return { session, error, addSet, updateSet, deleteSet /* ... */ }
179+
}
180+
```
181+
182+
Trigger: extract a hook when there are >= 2 mutations sharing loading/error state, or non-trivial derived state. A single piece of `useState` stays inline.
183+
184+
### 6. Text and dates through `next-intl`
185+
186+
- Client component: `const t = useTranslations('namespace')`.
187+
- Server component: `const t = await getTranslations('namespace')`; dates via `const format = await getFormatter()` (never hardcode a locale like `'pl-PL'`).
188+
- Add keys to BOTH `messages/pl.json` and `messages/en.json`.
189+
- Plain ASCII in UI strings (see the `code-style` skill).
190+
191+
---
192+
193+
## Key rules
194+
195+
| Rule | Detail |
196+
|---|---|
197+
| Location first | `ui/` (generic) vs `common/` (shared app) vs `{domain}/{feature}/` (domain) |
198+
| No domain deps in `ui/` | If it imports a domain type/helper, it is not a UI primitive |
199+
| One folder + barrel per component | `{name}/{name}.tsx` + `index.ts` everywhere - `ui/`, `common/`, and feature `components/{name}/` |
200+
| Only `{feature}.tsx` at a feature root | All sub-components live under `{feature}/components/{name}/` - never a flat sub-component `.tsx` |
201+
| Classes in `class-names.ts` | Promote any class string reused 2+ times; use `joinClasses` + `variant` props |
202+
| Pages = server + loader | Fetch in `src/loaders/`, render with `PageContainer`/`PageHeader`; never query in JSX |
203+
| `'use client'` only when needed | State, effects, handlers - otherwise keep it a server component |
204+
| Hooks inside the feature | `{feature}/hooks/use-*.ts`, not a global hooks folder |
205+
| i18n both locales | Every key in `messages/pl.json` AND `messages/en.json`; dates via `getFormatter` |
206+
207+
---
208+
209+
## Naming conventions
210+
211+
| What | Convention | Example |
212+
|---|---|---|
213+
| Component folder / file | `kebab-case` | `page-header/`, `exercise-header.tsx` |
214+
| Exported component | `PascalCase` | `PageHeader`, `ExerciseCard` |
215+
| Barrel | `index.ts` re-export | `export { Field } from './field'` |
216+
| Hook file / function | `use-{feature}-{type}.ts` / `use{Feature}{Type}` | `use-workout-session.ts` / `useWorkoutSession` |
217+
| Loader function | `load{Feature}` camelCase | `loadTrainingPlans` |
218+
| Variant prop | `variant` union, mapped to class via `Record` | `variant: 'primary' \| 'secondary'` |
219+
| Class-name export | `{name}Class` / `{name}Class(arg)` | `primaryButtonClass`, `statusBadgeClass(status)` |

.ai/skills/spec-writing/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ See references/spec-checklist.md
9898
## Reference Materials
9999

100100
- [Spec Checklist](references/spec-checklist.md)
101-
- [Compliance Review](references/compliance-review.md)
101+
- [Compliance Review — dispatcher](references/compliance-review.md) — load first, then load the stack-specific file below
102+
- [Compliance Review — Medusa.js](references/compliance-review-medusa.md) — for Medusa.js projects
103+
- [Compliance Review — Payload CMS](references/compliance-review-payload.md) — for Payload CMS / Next.js projects
102104
- [Specification Template](references/spec-template.md)
103105
- [Root AGENTS.md](../../../AGENTS.md)
104106
- [Medusa Magento Example](https://docs.medusajs.com/resources/integrations/guides/magento)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Compliance Review — Medusa.js
2+
3+
Run this after the spec checklist passes. Validates the spec against Medusa.js hard rules.
4+
5+
---
6+
7+
## Compliance Matrix
8+
9+
| Rule | Source | Status | Notes |
10+
|------|--------|--------|-------|
11+
| Module name is camelCase | AGENTS.md | ✅ / ❌ / N/A | |
12+
| No PUT/PATCH methods | AGENTS.md | ✅ / ❌ / N/A | |
13+
| All mutations through workflow | AGENTS.md | ✅ / ❌ / N/A | |
14+
| No module service called from route | AGENTS.md | ✅ / ❌ / N/A | |
15+
| Prices not multiplied/divided by 100 | AGENTS.md | ✅ / ❌ / N/A | |
16+
| Static imports only | AGENTS.md | ✅ / ❌ / N/A | |
17+
| `transform()` used for data manipulation in workflows | AGENTS.md | ✅ / ❌ / N/A | |
18+
| `external_id` for idempotency | AGENTS.md | ✅ / ❌ / N/A | |
19+
| Zod validation on all API inputs | AGENTS.md | ✅ / ❌ / N/A | |
20+
| Built-in Medusa workflows preferred over custom | Medusa docs | ✅ / ❌ / N/A | |
21+
| `query.graph()` for cross-module reads | AGENTS.md | ✅ / ❌ / N/A | |
22+
| `query.index()` for cross-module filtering | AGENTS.md | ✅ / ❌ / N/A | |
23+
24+
---
25+
26+
## Verdict
27+
28+
- **APPROVED** — All rules pass or are explicitly N/A with justification.
29+
- **CHANGES REQUIRED** — List violations below.
30+
31+
### Violations (if any)
32+
33+
-
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Compliance Review — Payload CMS / Next.js
2+
3+
Run this after the spec checklist passes. Validates the spec against Payload CMS and Next.js project hard rules.
4+
5+
---
6+
7+
## Compliance Matrix
8+
9+
| Rule | Source | Status | Notes |
10+
|------|--------|--------|-------|
11+
| New collection registered in `payload.config.ts` and exported from `collections/index.ts` | AGENTS.md | ✅ / ❌ / N/A | |
12+
| Migration generated (`migrate:create`) and applied (`migrate`) after schema change | AGENTS.md | ✅ / ❌ / N/A | |
13+
| `yarn generate:types` run after schema change | AGENTS.md | ✅ / ❌ / N/A | |
14+
| `overrideAccess: true` used only in server-side loaders, never in client-side handlers | security | ✅ / ❌ / N/A | |
15+
| Auth-sensitive IDs always sourced from DB doc, never from HTTP request params | security | ✅ / ❌ / N/A | |
16+
| All 4 access operations (create, read, update, delete) explicitly defined on every collection | convention | ✅ / ❌ / N/A | |
17+
| Custom admin components added to importMap (`yarn generate:importmap`) | Payload | ✅ / ❌ / N/A | |
18+
| `yarn build` run after each phase | AGENTS.md | ✅ / ❌ / N/A | |
19+
| User-facing strings only via i18n (`messages/pl.json` + `en.json`), never hardcoded | convention | ✅ / ❌ / N/A | |
20+
21+
---
22+
23+
## Verdict
24+
25+
- **APPROVED** — All rules pass or are explicitly N/A with justification.
26+
- **CHANGES REQUIRED** — List violations below.
27+
28+
### Violations (if any)
29+
30+
-

.ai/skills/spec-writing/references/compliance-review.md

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
11
# Final Compliance Review
22

3-
Run this after the spec checklist passes. Validates the spec against the hard rules in `AGENTS.md`.
3+
Run this after the spec checklist passes. Load the tech-specific compliance file for the project's stack, then fill in the matrix.
44

55
---
66

7-
## Compliance Matrix
8-
9-
| Rule | Source | Status | Notes |
10-
|------|--------|--------|-------|
11-
| Module name is camelCase | AGENTS.md | ✅ / ❌ / N/A | |
12-
| No PUT/PATCH methods | AGENTS.md | ✅ / ❌ / N/A | |
13-
| All mutations through workflow | AGENTS.md | ✅ / ❌ / N/A | |
14-
| No module service called from route | AGENTS.md | ✅ / ❌ / N/A | |
15-
| Prices not multiplied/divided by 100 | AGENTS.md | ✅ / ❌ / N/A | |
16-
| Static imports only | AGENTS.md | ✅ / ❌ / N/A | |
17-
| `transform()` used for data manipulation in workflows | AGENTS.md | ✅ / ❌ / N/A | |
18-
| `external_id` for idempotency | AGENTS.md | ✅ / ❌ / N/A | |
19-
| Zod validation on all API inputs | AGENTS.md | ✅ / ❌ / N/A | |
20-
| Built-in Medusa workflows preferred over custom | Medusa docs | ✅ / ❌ / N/A | |
21-
| `query.graph()` for cross-module reads | AGENTS.md | ✅ / ❌ / N/A | |
22-
| `query.index()` for cross-module filtering | AGENTS.md | ✅ / ❌ / N/A | |
7+
## Which file to load
8+
9+
| Stack | File |
10+
|-------|------|
11+
| Medusa.js | [compliance-review-medusa.md](compliance-review-medusa.md) |
12+
| Payload CMS / Next.js | [compliance-review-payload.md](compliance-review-payload.md) |
13+
14+
If the project uses a stack not listed above, create a new `compliance-review-[stack].md` in this directory and add it to the table.
15+
16+
---
17+
18+
## Universal checks (all stacks)
19+
20+
These apply regardless of technology:
21+
22+
| Check | Status | Notes |
23+
|-------|--------|-------|
24+
| No auth-sensitive values accepted from HTTP request params | ✅ / ❌ / N/A | |
25+
| All user-facing strings go through i18n, never hardcoded | ✅ / ❌ / N/A | |
26+
| `yarn build` (or equivalent) verified after each phase | ✅ / ❌ / N/A | |
27+
| Open Questions section removed or all items resolved | ✅ / ❌ / N/A | |
2328

2429
---
2530

2631
## Verdict
2732

28-
- **APPROVED** — All rules pass or are explicitly N/A with justification.
33+
- **APPROVED** — All rules (universal + stack-specific) pass or are explicitly N/A with justification.
2934
- **CHANGES REQUIRED** — List violations below.
3035

3136
### Violations (if any)

.ai/skills/spec-writing/references/spec-template.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ workflowName
134134

135135
---
136136

137+
## Spec Checklist
138+
139+
See [spec-checklist.md](../references/spec-checklist.md). Paste results here before marking status as `review`.
140+
141+
---
142+
143+
## Compliance Review
144+
145+
{Load [compliance-review.md](../references/compliance-review.md) to determine which stack-specific file to use, then paste the filled matrix here.}
146+
147+
---
148+
137149
## Open Questions
138150

139151
- [ ] {Unresolved decision — remove when answered}

0 commit comments

Comments
 (0)