Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions .ai/specs/2026-06-16-share-links.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,17 @@ src/
│ └── share-links/
│ └── index.ts # new collection
├── app/
│ ├── (payload)/
│ │ └── admin/modules/share-link-url/
│ │ ├── share-link-url.tsx # custom UI component (admin)
│ │ └── index.ts
│ └── [locale]/(frontend)/
│ └── share/
│ └── [token]/
│ └── page.tsx # public share page
└── loaders/
└── share-link-loader.ts # token validation + data fetching
└── modules/
└── sharing/
├── admin/share-link-url/
│ ├── share-link-url.tsx # custom UI component (admin)
│ └── index.ts
└── server/
└── load-share-link.ts # token validation + data fetching
```

### Modified files
Expand Down Expand Up @@ -89,7 +90,7 @@ src/

## Token Validation (loader)

`loadShareLink(token: string)` in `src/loaders/share-link-loader.ts`:
`loadShareLink(token: string)` in `src/modules/sharing/server/load-share-link.ts`:

1. Query `share-links` with `where: { token: { equals: token } }`, `overrideAccess: true`, `limit: 1`
2. If no document found → return `null`
Expand Down Expand Up @@ -166,16 +167,16 @@ Build the loader, the share page, and any read-only variants of existing workout

### Phase 2

- [ ] Create `src/app/(payload)/admin/modules/share-link-url/share-link-url.tsx` — client component using `useFormFields` to read `token`, compose URL from `NEXT_PUBLIC_SERVER_URL`, render copy button
- [ ] Create `src/app/(payload)/admin/modules/share-link-url/index.ts` — re-export
- [ ] Create `src/modules/sharing/admin/share-link-url/share-link-url.tsx` — client component using `useFormFields` to read `token`, compose URL from `NEXT_PUBLIC_SERVER_URL`, render copy button
- [ ] Create `src/modules/sharing/admin/share-link-url/index.ts` — re-export
- [ ] Wire component into `shareUrl` ui field in the collection
- [ ] Run `yarn generate:importmap`
- [ ] Run `yarn build` — verify no type errors

### Phase 3

- [ ] Add `share` namespace keys to `messages/pl.json` and `messages/en.json`
- [ ] Create `src/loaders/share-link-loader.ts`
- [ ] Create `src/modules/sharing/server/load-share-link.ts`
- [ ] Create `src/app/[locale]/(frontend)/share/[token]/page.tsx`
- [ ] Adapt plan components to support `readOnly` prop (hide set forms, logging buttons) — or create a read-only wrapper
- [ ] Run `yarn build` — verify no type errors
Expand Down
12 changes: 6 additions & 6 deletions .ai/specs/2026-06-20-exercise-client-note.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Today a client can only add a note **per set** (`set-logs.note`). We add a new l

## Problem Statement

In the workout tracker a client logs exercise execution as sets (`set-logs`). Each set can have its own note (`set-logs.note`, the field in [SeriesForm](../../src/components/workout/series-form/series-form.tsx)). What is missing is a note **for the whole exercise** — an annotation like "shoulders felt weak today, lower the weight next time" that applies to the exercise as a whole, not to one specific set.
In the workout tracker a client logs exercise execution as sets (`set-logs`). Each set can have its own note (`set-logs.note`, the field in [SeriesForm](../../src/modules/training/components/series-form/series-form.tsx)). What is missing is a note **for the whole exercise** — an annotation like "shoulders felt weak today, lower the weight next time" that applies to the exercise as a whole, not to one specific set.

The note cannot live in the plan: `workout-exercise-rows` has `update: isAdmin` — clients do not write to the template. It must therefore be created in the log layer, tied to the **(session, exercise)** pair.

Expand Down Expand Up @@ -111,7 +111,7 @@ src/
│ │ └── index.ts # NEW collection
│ └── index.ts # + re-export ExerciseLogs
├── payload.config.ts # + register in collections[]
├── components/workout/
├── modules/training/components/
│ ├── workout-tracker/
│ │ ├── workout-tracker.tsx # pass note + onSaveNote to the card
│ │ └── hooks/
Expand Down Expand Up @@ -192,7 +192,7 @@ useWorkoutSession (hook)
└── update local state
```

UI: [workout-tracker.tsx](../../src/components/workout/workout-tracker/workout-tracker.tsx) passes `note` + `onSaveNote` to [exercise-card.tsx](../../src/components/workout/exercise-card/exercise-card.tsx), which renders the note in the exercise header (distinct from the plan note) and — when `!readOnly` — exposes an editable field.
UI: [workout-tracker.tsx](../../src/modules/training/components/workout-tracker/workout-tracker.tsx) passes `note` + `onSaveNote` to [exercise-card.tsx](../../src/modules/training/components/exercise-card/exercise-card.tsx), which renders the note in the exercise header (distinct from the plan note) and — when `!readOnly` — exposes an editable field.

---

Expand Down Expand Up @@ -220,13 +220,13 @@ Render and edit the note in `ExerciseCard`, strings in `pl.json`/`en.json`, visi
- [x] `yarn payload migrate` — run by the user (DB-mutating step).

### Phase 2 — Runtime
- [x] [use-workout-session.ts](../../src/components/workout/workout-tracker/hooks/use-workout-session.ts): load `exercise-logs` by `session` (parallel with set-logs), add `exerciseNotes` state.
- [x] [use-workout-session.ts](../../src/modules/training/components/workout-tracker/hooks/use-workout-session.ts): load `exercise-logs` by `session` (parallel with set-logs), add `exerciseNotes` state.
- [x] Add the `noteForRow(rowId)` selector and the `saveExerciseNote(ex, note)` mutation (upsert).
- [x] Expose both in the hook's returned API.

### Phase 3 — UI + i18n
- [x] [workout-tracker.tsx](../../src/components/workout/workout-tracker/workout-tracker.tsx): pass `clientNote` + `onSaveNote` to `ExerciseCard`.
- [x] [exercise-card.tsx](../../src/components/workout/exercise-card/exercise-card.tsx): render the client note in the header (new `exercise-note` subcomponent) + allow editing when `!readOnly`.
- [x] [workout-tracker.tsx](../../src/modules/training/components/workout-tracker/workout-tracker.tsx): pass `clientNote` + `onSaveNote` to `ExerciseCard`.
- [x] [exercise-card.tsx](../../src/modules/training/components/exercise-card/exercise-card.tsx): render the client note in the header (new `exercise-note` subcomponent) + allow editing when `!readOnly`.
- [x] Strings (`addNote`, `notePlaceholder`, `saveNote`, `cancelNote`) in `messages/pl.json` + `messages/en.json`.
- [x] [export-seed.ts](../../src/scripts/export-seed.ts): comment updated (logs already excluded via the allow-list).
- [x] Typecheck (`tsc --noEmit`) + lint pass.
Expand Down
5 changes: 5 additions & 0 deletions .changeset/admin-training-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'training-app': minor
---

Add plan and workout structure navigation in the admin panel and fix exercise catalog selection in the structure editor.
5 changes: 5 additions & 0 deletions .changeset/exercise-target-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'training-app': minor
---

Allow workout exercises to use either repetition or duration targets.
5 changes: 5 additions & 0 deletions .changeset/green-sides-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'training-app': minor
---

Record left and right weights and repetitions separately for strength sets and exercise defaults.
5 changes: 5 additions & 0 deletions .changeset/group-protocol-meta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'training-app': minor
---

Show group protocol details above exercises in workout tracking.
5 changes: 5 additions & 0 deletions .changeset/modular-training-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'training-app': patch
---

Reorganize training and sharing code into domain-oriented business modules. Move frontend components, server queries, and Payload Admin extensions behind explicit module boundaries without changing user-facing behavior.
5 changes: 5 additions & 0 deletions .changeset/standardize-rest-duration-labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'training-app': patch
---

Standardize rest duration labels as `Rest(s)` in workout group configuration.
87 changes: 77 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ Operational guide for AI agents working in this repository.
## Before Writing Code

- Write everything in English: code, comments, variable names, documentation.
- Do not implement code, schema, migration, script, configuration, or documentation changes unless the user explicitly authorizes implementation with "wdrażamy" or an equivalent clear instruction. Analysis, investigation, and recommendations do not authorize changes.
- Check `.ai/specs/` before coding any non-trivial feature.
- Skills are installed in `.claude/skills/` (Claude Code) and `.agents/skills/` (Codex). `.agents/skills/` is the local source of truth — edit a skill **only** there, then run `ags push-skill` to propagate it to the source repo (it also syncs the `.claude/skills/` copy). See Installing skills.
- Prefer minimal, focused changes. Do not refactor code outside the task scope.
- Run `yarn build` after every implementation to catch type errors.
- Do not run `yarn build` automatically after implementation. Run it only when the user explicitly asks.
- Comment and naming conventions are defined in the `code-style` skill. Load it before writing or reviewing TypeScript.

---
Expand All @@ -23,7 +24,7 @@ Match the task to the table before starting. A single task often maps to multipl
|---|---|
| Creating a new collection or extending the schema | Load skill `payload-build-collections` |
| Adding a custom admin view, tab, or field UI | Load skill `payload-build-modules` |
| Building a front-end component or page (`src/components`, `(frontend)`) | Load skill `payload-frontend-build-components` |
| Building a front-end component or page (`src/components`, `src/modules/*/components`, `(frontend)`) | Load skill `payload-frontend-build-components` |
| Debugging hooks, queries, access control, transactions | Load skill `payload` |
| Security review, or adding/modifying auth, access control, uploads, CORS/CSRF, headers | Load skill `payload-security`; keep `.ai/audits/security-audit.md` current |
| Writing questions for a client or stakeholder | Load skill `writing-questions` |
Expand Down Expand Up @@ -97,16 +98,19 @@ src/
│ └── (payload)/ # Payload admin routes and API
├── collections/ # One folder per collection (index.ts + optional hooks.ts, types.ts)
├── components/
│ ├── common/
│ ├── ui/
│ └── workout/
│ ├── common/ # App-wide, non-domain components
│ └── ui/ # Domain-agnostic UI primitives
├── data/ # Static/seed data
├── i18n/ # next-intl config
├── lib/ # Utilities and SDK client
├── loaders/ # Server-side data fetching
├── lib/ # Business-agnostic utilities and SDK client
├── migrations/ # Payload DB migrations (auto-generated)
├── modules/ # Vertical business modules
│ └── <module>/
│ ├── <area>/ # Domain area (types, constants, rules)
│ ├── components/ # Module-owned frontend components
│ ├── server/ # Server-only queries and use cases
│ └── admin/ # Module-owned Payload Admin UI
├── scripts/ # One-off CLI scripts
├── types/
├── payload-types.ts # Auto-generated — do not edit
└── payload.config.ts
.claude/skills/ # Skills for Claude Code
Expand All @@ -118,19 +122,82 @@ src/

---

## Module Architecture

- `src/modules/` is organized by business capability, not by Payload collection. A module
is a vertical slice that may own domain rules, frontend components, server queries, and
Payload Admin UI.
- Closely related concepts belong to areas inside one module. For example, `training`
contains `exercises`, `plans`, and `logs`; do not create a separate top-level module for
every table or entity.
- Keep `src/app/` thin: route files compose module entry points but do not own feature
implementations. Modules must never import from `src/app/`.
- Keep Payload `CollectionConfig`, fields, hooks, access control, relationships, and
indexes in `src/collections/`. Collections may import only client-safe domain APIs from
modules, never module components, admin UI, or server entry points.
- Put domain-specific React components under `src/modules/<module>/components/`. Keep only
domain-agnostic primitives in `src/components/ui/` and cross-module application UI in
`src/components/common/`.
- Put server-side queries and use cases under an explicit `server/` entry point and add
`import 'server-only'`. Do not re-export server code from a client-safe module barrel.
- Put Payload Admin implementations under `src/modules/<module>/admin/<feature>/`.
Collection configuration must reference the exact component file and the import map
must be regenerated after a path changes.
- Use `types.ts` only for types and interfaces, and `constants.ts` only for constants and
declarative configuration. Name operation files after their responsibility; do not add
generic `utils.ts`, `helpers.ts`, or `models.ts` files.
- Code outside a domain area must use its public `index.ts`. Environment-specific APIs use
explicit subpaths such as `@/modules/training/plans/server` or
`@/modules/training/components/workout-plans`.
- Cross-module dependencies must use public entry points, remain one-directional, and
stay cycle-free. For example, `sharing` may use the public `training/plans/server` API;
`training` must not depend back on `sharing`.
- Keep only business-agnostic technical functions and SDK clients in `src/lib/`.

---

## Payload-Generated Types

- `src/payload-types.ts` is generated by Payload from collection, field, global, and
`payload.config.ts` definitions. Never edit it manually; regenerate it with
`yarn generate:types`.
- A type declared in a module may use the same name as a generated Payload type. Types
exported from different files do not conflict merely because their names match.
- A naming conflict occurs only when two types with the same local name are imported
into the same scope. Alias the imports when both representations are needed, for
example `Workout as PayloadWorkout` and `Workout as PlanWorkout`.
- Name module-owned types according to their domain role. Do not add prefixes or
suffixes solely to avoid a same-name type exported by another module.

---

## Key Commands

```bash
yarn dev # start dev server
yarn build # production build — run after every implementation
yarn payload migrate # run pending DB migrations
yarn build # production build - run only when explicitly requested
yarn payload migrate:create # generate a migration after a schema change - run manually
yarn payload migrate # run pending DB migrations - run manually
yarn generate:types # regenerate payload-types.ts
yarn generate:importmap # regenerate admin import map (after adding custom views)
yarn seed # seed demo data
yarn lint # ESLint
npx skills add <src> -a claude-code -a codex --copy # install skills
```

## Database Migrations

- Generate migrations with `yarn payload migrate:create` after every schema change.
- Maintainers run `yarn payload migrate:create` and `yarn payload migrate` manually. Agents must not run either command.
- Agents must never create migration files manually or edit Payload-generated migration files.
- Keep Payload-generated schema migrations in their generated form. Do not add manual SQL or data updates to them.

## Data Backfills

- Implement every data backfill as a separate, idempotent script through the Payload Local API.
- Do not add data backfill SQL to Payload-generated schema migrations.
- Maintainers run backfill scripts manually. Agents must not run them.

---

## Release
Expand Down
Loading
Loading