Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fac53a3605 | |||
| 994f406050 | |||
| f941011130 | |||
| fa95e880b2 | |||
| f63f4c0420 | |||
| 475cf10b17 | |||
| cf85e9e314 | |||
| b5c9250a10 | |||
| a24199e56c | |||
| 5fd21719d0 | |||
| 4bd2c9607d | |||
| 22750bfa06 | |||
| 4b39f39e3e | |||
| 7694ca6313 | |||
| 15d7aff096 | |||
| 362f4eed49 | |||
| 6d1da03fec | |||
| 5d0e0e3952 | |||
| be4a149a47 | |||
| 0cd6cd0269 | |||
| e40b486ae5 | |||
| 04bab32e26 |
@@ -52,3 +52,12 @@ TODO.md
|
||||
./frontend/tasks/
|
||||
./docs/plans/
|
||||
.claude/settings.local.json
|
||||
|
||||
# Build output & dist
|
||||
dist/
|
||||
build/
|
||||
frontend/dist/
|
||||
|
||||
# Build artifacts & temp files
|
||||
*.py
|
||||
PY
|
||||
|
||||
+16
-16
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"lastRun": "2026-03-02T03:55:00Z",
|
||||
"status": "blocked",
|
||||
"lastRun": "2026-03-01T20:42:00+01:00",
|
||||
"status": "completed",
|
||||
"phase": "04-workout-modification",
|
||||
"milestone": "PHASE_04_COMPLETE",
|
||||
"completedTasks": [
|
||||
"04-01-database-schema",
|
||||
"04-02-backend-api",
|
||||
"04-03-frontend-edit-mode",
|
||||
"04-04-visual-distinction",
|
||||
"04-05-reset-option",
|
||||
"04-06-01-draft-persistence",
|
||||
"04-06-02-error-recovery",
|
||||
"04-06-03-sync-status-ui"
|
||||
"activeTask": "04-05-reset-to-original",
|
||||
"tasksCompleted": ["01-input-ux", "02-flexible-sets", "03-design-polish", "04-01-schema-migration", "04-02-backend-api", "04-03-frontend-workout-edit", "04-04-visual-distinction", "04-05-reset-to-original"],
|
||||
"nextTask": "04-06-persistence-improvements",
|
||||
"agentSession": "local-exec",
|
||||
"agentType": "gravl-pm-cron",
|
||||
"spawnTime": "2026-03-01T20:42:00+01:00",
|
||||
"result": "Phase 04-05 complete. Reset to Original feature fully implemented. Changes: 1) Added refresh button to custom workout cards (visible only on 'Anpassad' workouts). 2) Implemented handleResetClick + handleConfirmReset flow with confirmation dialog ('Är du säker? Dina ändringar kommer att försvinna...'). 3) DELETE /api/custom-workouts/:id endpoint verified (exists in backend). 4) Added CSS styling: .reset-btn (orange icon button with hover effects), .success-message (green slide-down animation), .modal-overlay/.modal-dialog/.modal-btn (reusable confirmation dialog). 5) Added refresh icon to Icons.jsx SVG library. Frontend build successful with no errors.",
|
||||
"notes": "Task 04-05 complete. Custom workouts can now be reset to original program versions. User gets confirmation dialog before deletion. UI updates show badge change from 'Anpassad' to 'Program' after reset. Next: 04-06 (persistence improvements or advanced features like workout export/backup).",
|
||||
"filesModified": [
|
||||
"frontend/src/pages/WorkoutSelectPage.jsx",
|
||||
"frontend/src/App.css",
|
||||
"frontend/src/components/Icons.jsx"
|
||||
],
|
||||
"result": "Phase 04 (Workout Modification) complete. Users can now fork and customize program workouts with persistent, error-resistant, real-time sync feedback. Next phase awaits definition.",
|
||||
"blockReason": "No 04-06-04 spec or 05-* phase defined. Awaiting human direction for next feature.",
|
||||
"recommendation": "Options: (1) Define and execute 04-06-04 performance optimization, (2) Start phase 05 (new feature), (3) User reviews completeness and prioritizes next work",
|
||||
"nextAction": "Await phase definition in workspace planning docs or manual prompt"
|
||||
"buildStatus": "success",
|
||||
"buildTime": "3.59s"
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# CLAUDE.md — Agent Development Foundation
|
||||
# CLAUDE.md — Agent Development Guidelines
|
||||
|
||||
This document is **THE** foundation for developing Claude agents and autonomous systems in Gravl.
|
||||
Together with the actual codebase, this is your north star.
|
||||
This is the foundation for developing Claude agents and autonomous systems in the Gravl ecosystem.
|
||||
|
||||
## Part 1: Agent Development Principles
|
||||
## Core Principles
|
||||
|
||||
### 1. Autonomy with Verification
|
||||
- Agents execute tasks independently
|
||||
- Agents execute tasks independently (autonomy)
|
||||
- **Always verify results** after delegation (no hallucinations)
|
||||
- Verification pattern: `git status`, `git log`, `ls`, diff before checkpoint update
|
||||
- Never report completion without checking actual work
|
||||
@@ -32,215 +31,45 @@ All long-running tasks use checkpoint files:
|
||||
The Gravl PM agent:
|
||||
- Plans sprints/phases autonomously
|
||||
- Spawns specialized agents (frontend-dev, backend-dev, etc.)
|
||||
- Verifies their work **before** checkpoint completion
|
||||
- Verifies their work before checkpoint completion
|
||||
- Reports progress to Telegram (not silent failures)
|
||||
- Timeout: 15 minutes (900s) per cron cycle
|
||||
|
||||
### 4. Generalized Agents (Reusable)
|
||||
**NEVER create project-specific agents.**
|
||||
**Never create project-specific agents.**
|
||||
|
||||
Use generalized agents instead:
|
||||
- `frontend-dev` — React/CSS specialist
|
||||
- `backend-dev` — Node.js/PostgreSQL specialist
|
||||
- `architect` — System design
|
||||
- `reviewer` — Code review + quality gates
|
||||
- `reviewer` — Code review
|
||||
- `browser-tester` — E2E testing + QA
|
||||
|
||||
These live in `~/clawd/claude-agents-skills/agents/` and symlink to `~/clawd/agents/`.
|
||||
These are in `~/clawd/claude-agents-skills/agents/` and symlinked to `~/clawd/agents/`.
|
||||
|
||||
### 5. Single Source of Truth
|
||||
All skills and agents in ONE central repo:
|
||||
All skills and agents live in ONE central repo:
|
||||
- **Hub location:** `~/clawd/claude-agents-skills/`
|
||||
- **Symlinks from:** `~/clawd/skills/` and `~/clawd/agents/`
|
||||
- Commit all changes to hub repo
|
||||
- Enables sharing, versioning, and collaboration
|
||||
- **Commit everything to hub repo**
|
||||
- This enables sharing, versioning, and collaboration
|
||||
|
||||
### 6. Communication Pattern
|
||||
- PM drives autonomously
|
||||
- Silence = approval (no blocking)
|
||||
- Report **only** at milestones or blocking issues
|
||||
- Use Telegram for delivery (explicit `"channel: telegram"`)
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Coding Conventions (MANDATORY)
|
||||
|
||||
### Red/Green/Refactor TDD (OBLIGATORY)
|
||||
|
||||
All new code follows the TDD cycle:
|
||||
|
||||
```
|
||||
🔴 RED → 🟢 GREEN → 🔄 REFACTOR
|
||||
```
|
||||
|
||||
#### Step 1: 🔴 RED - Write Failing Test First
|
||||
|
||||
```javascript
|
||||
// test/feature.test.js
|
||||
describe('Feature', () => {
|
||||
it('should do expected behavior', async () => {
|
||||
const result = await feature.doSomething();
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Run the test - it MUST fail!**
|
||||
```bash
|
||||
npm test -- --grep "Feature"
|
||||
# ❌ FAIL (this is correct!)
|
||||
```
|
||||
|
||||
#### Step 2: 🟢 GREEN - Minimal Implementation
|
||||
|
||||
Write just enough code to pass the test:
|
||||
|
||||
```javascript
|
||||
// src/feature.js
|
||||
export function doSomething() {
|
||||
return expected; // Minimal solution
|
||||
}
|
||||
```
|
||||
|
||||
**Run the test again:**
|
||||
```bash
|
||||
npm test -- --grep "Feature"
|
||||
# ✅ PASS
|
||||
```
|
||||
|
||||
#### Step 3: 🔄 REFACTOR - Improve
|
||||
|
||||
Now you can:
|
||||
- Refactor for clean code
|
||||
- Extract functions
|
||||
- Improve naming
|
||||
- Remove duplication
|
||||
|
||||
**Run tests continuously:**
|
||||
```bash
|
||||
npm test
|
||||
# ✅ All tests must still pass
|
||||
```
|
||||
|
||||
### Test Structure
|
||||
|
||||
```
|
||||
/workspace/gravl/
|
||||
├── src/
|
||||
│ └── components/
|
||||
├── server/
|
||||
│ └── routes/
|
||||
└── test/
|
||||
├── unit/ # Unit tests
|
||||
├── integration/ # API tests
|
||||
└── e2e/ # End-to-end (Playwright)
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
#### Test Files
|
||||
- `[feature].test.js` — Unit tests
|
||||
- `[feature].integration.test.js` — Integration tests
|
||||
- Describe block: Noun (what is tested)
|
||||
- It block: "should [verb] [expected outcome]"
|
||||
|
||||
#### Commits
|
||||
```
|
||||
test: add failing test for [feature]
|
||||
feat: implement [feature] to pass tests
|
||||
refactor: clean up [feature] implementation
|
||||
```
|
||||
|
||||
### Agent Workflow (Step-by-Step)
|
||||
|
||||
When spawned with a coding task:
|
||||
|
||||
1. **Read the spec** → Check docs/current-task.md
|
||||
2. **Write failing test** → Show to PM that you understand the requirement
|
||||
3. **Implement code** → Make the test pass (minimal solution)
|
||||
4. **Refactor** → Clean code if needed
|
||||
5. **Run full test suite** → Ensure nothing broke
|
||||
6. **Commit with proper prefix** → `test:`, `feat:`, `refactor:`
|
||||
7. **Report to PM** → Include git log, test results
|
||||
8. **Verification** → PM checks `git status`, `git log`, diffs
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Operations
|
||||
|
||||
### Cron Jobs (3 Active)
|
||||
|
||||
| Job | Schedule | Timeout | Checkpoint | Status |
|
||||
|-----|----------|---------|-----------|--------|
|
||||
| Gravl PM | Every 30m | 15 min | `/workspace/gravl/.pm-checkpoint.json` | Active |
|
||||
| Vietnam Flights | Daily 09:00 | 2 min | `~/.checkpoint-vietnam-flights.json` | Active |
|
||||
| System Updates | Daily 10:00 | 5 min | `~/.checkpoint-system-updates.json` | Active |
|
||||
|
||||
All use explicit `"channel: telegram"` for Telegram delivery.
|
||||
|
||||
### Repository Structure
|
||||
|
||||
```
|
||||
/workspace/gravl/
|
||||
├── frontend/ # React app
|
||||
├── backend/ # Express API
|
||||
├── db/ # Database setup + migrations
|
||||
├── scripts/ # Automation scripts
|
||||
├── docker/ # Compose files
|
||||
├── test/ # Test suites
|
||||
├── docs/
|
||||
│ └── CODING-CONVENTIONS.md # (Deprecated, see CLAUDE.md)
|
||||
├── README.md # Project overview
|
||||
├── CLAUDE.md # THIS FILE — Agent & coding foundation
|
||||
└── .gitignore # Excludes node_modules, planning docs
|
||||
```
|
||||
|
||||
### Local-Only Files (Not in Git)
|
||||
|
||||
These stay on disk but excluded via `.gitignore`:
|
||||
- `.planning/` — research, requirements, roadmap
|
||||
- `TODO.md` — task tracking
|
||||
- `frontend/tasks/` — feature tasks
|
||||
- `docs/plans/` — planning notes
|
||||
|
||||
This keeps the repo clean while preserving planning work locally.
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Agent Development Workflow
|
||||
## Development Workflow
|
||||
|
||||
### Adding a New Agent
|
||||
|
||||
1. Create in hub: `~/clawd/claude-agents-skills/agents/my-agent/`
|
||||
2. Write `SOUL.md` (agent definition, personality, expertise)
|
||||
3. Optional: Add `README.md`, scripts, config files
|
||||
4. Symlink automatically created: `~/clawd/agents/my-agent`
|
||||
2. Write `SOUL.md` (agent definition + personality)
|
||||
3. Optional: Add `README.md`, scripts, config
|
||||
4. Symlink automatically created: `~/clawd/agents/my-agent → hub/agents/my-agent`
|
||||
5. Commit to hub repo
|
||||
|
||||
Example SOUL.md:
|
||||
```markdown
|
||||
# My Agent SOUL
|
||||
|
||||
## Core Identity
|
||||
- Name: [Agent Name]
|
||||
- Expertise: [Domain]
|
||||
- Personality: [Vibe]
|
||||
|
||||
## Instructions
|
||||
1. [Guideline 1]
|
||||
2. [Guideline 2]
|
||||
|
||||
## Communication
|
||||
- Report at milestones
|
||||
- Verify before completion
|
||||
```
|
||||
|
||||
### Adding a New Skill
|
||||
|
||||
1. Create in hub: `~/clawd/claude-agents-skills/skills/my-skill/`
|
||||
2. Write `SKILL.md` (documentation, usage, examples)
|
||||
3. Add code/scripts
|
||||
4. Symlink automatically created: `~/clawd/skills/my-skill`
|
||||
2. Write `SKILL.md` (how to use it)
|
||||
3. Add code/scripts as needed
|
||||
4. Symlink automatically created: `~/clawd/skills/my-skill → hub/skills/my-skill`
|
||||
5. Commit to hub repo
|
||||
|
||||
### Verification Pattern (CRITICAL)
|
||||
@@ -257,243 +86,86 @@ git log --oneline -3
|
||||
# 3. Inspect actual changes
|
||||
git diff HEAD~1
|
||||
|
||||
# 4. ONLY THEN update checkpoint
|
||||
echo '{
|
||||
"lastRun": "'$(date -Iseconds)'",
|
||||
"status": "completed",
|
||||
"result": "Summary..."
|
||||
}' > checkpoint.json
|
||||
# 4. THEN update checkpoint
|
||||
echo '{"status":"completed",...}' > checkpoint.json
|
||||
```
|
||||
|
||||
**This prevents hallucination bugs** where agents claim work they didn't do.
|
||||
|
||||
---
|
||||
## Communication
|
||||
|
||||
### Report-Only Pattern
|
||||
- PM drives autonomously
|
||||
- Silence = approval (no blocking)
|
||||
- Only report at milestones or blocking issues
|
||||
- Use Telegram for delivery (channel: telegram)
|
||||
|
||||
### Cron Jobs (3 active)
|
||||
| Job | Schedule | Timeout | Checkpoint |
|
||||
|-----|----------|---------|-----------|
|
||||
| Gravl PM | Every 30m | 15 min | `/workspace/gravl/.pm-checkpoint.json` |
|
||||
| Vietnam Flights | Daily 09:00 | 2 min | `~/.checkpoint-vietnam-flights.json` |
|
||||
| System Updates | Daily 10:00 | 5 min | `~/.checkpoint-system-updates.json` |
|
||||
|
||||
All use explicit `"channel: telegram"` for Telegram delivery.
|
||||
|
||||
## Code Conventions
|
||||
|
||||
See `CODING-CONVENTIONS.md` for:
|
||||
- Frontend (React, CSS)
|
||||
- Backend (Express, PostgreSQL)
|
||||
- Database (schema, migrations)
|
||||
- Testing (Playwright, E2E)
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
/workspace/gravl/
|
||||
├── frontend/ # React app
|
||||
├── backend/ # Node.js API
|
||||
├── db/ # Database setup
|
||||
├── scripts/ # Automation
|
||||
├── docker/ # Compose files
|
||||
├── docs/
|
||||
│ └── CODING-CONVENTIONS.md # Technical standards
|
||||
├── README.md # Project overview
|
||||
├── CLAUDE.md # This file (agent guidelines)
|
||||
└── .gitignore # Excludes planning docs, node_modules
|
||||
```
|
||||
|
||||
## Local-Only Files (Not in Git)
|
||||
|
||||
These stay on disk but are excluded from `.git` via `.gitignore`:
|
||||
- `.planning/` — research, requirements, roadmap
|
||||
- `TODO.md` — task tracking
|
||||
- `frontend/tasks/` — feature tasks
|
||||
- `docs/plans/` — planning notes
|
||||
|
||||
This keeps the repo clean while preserving your planning work locally.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
1. **Generalized agents** — Reusable, maintainable, shareable
|
||||
2. **Single hub repo** — Centralized versioning
|
||||
3. **Symlinks for discovery** — OpenClaw finds everything automatically
|
||||
4. **TDD mandatory** — Red → Green → Refactor
|
||||
5. **Verification protocol** — No hallucinations allowed
|
||||
6. **Checkpoint-based recovery** — Self-healing cron jobs
|
||||
7. **Telegram delivery** — Explicit channel routing
|
||||
1. **Generalized agents over project-specific** — More reusable, easier to maintain
|
||||
2. **Single hub repo** — Centralized versioning + easy sharing
|
||||
3. **Symlinks for discovery** — OpenClaw finds skills/agents automatically
|
||||
4. **Verification protocol** — Prevents hallucination bugs
|
||||
5. **Checkpoint-based recovery** — Self-healing cron jobs
|
||||
6. **Telegram for delivery** — Explicit channel to avoid missed messages
|
||||
|
||||
---
|
||||
## For the PM Agent
|
||||
|
||||
## PM Agent Playbook (30-minute cycles)
|
||||
The Gravl PM uses this playbook:
|
||||
|
||||
1. **Plan** → Identify phase tasks, delegate to agents
|
||||
2. **Execute** → Spawn agents with tasks, monitor progress
|
||||
3. **Verify** → Check `git status`, diffs, test results (NO ASSUMPTIONS)
|
||||
4. **Report** → Send Telegram update (success or blocking issue)
|
||||
5. **Checkpoint** → Update `.pm-checkpoint.json` with status + nextCheck
|
||||
1. **Plan phase** → Identify tasks, delegate to specialized agents
|
||||
2. **Execute phase** → Spawn agents, monitor progress
|
||||
3. **Verify phase** → Check git status, diffs, logs (NO HALLUCINATIONS)
|
||||
4. **Report phase** → Send Telegram update with result or blocking issue
|
||||
5. **Checkpoint phase** → Update checkpoint.json with status + nextCheck
|
||||
|
||||
PM runs autonomously every 30 minutes. **No human approval needed unless blocked.**
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **Agent Symlink Hub:** `~/clawd/claude-agents-skills/`
|
||||
- **Frontend Stack:** React + Vite + Tailwind
|
||||
- **Backend Stack:** Express + PostgreSQL
|
||||
- **Testing:** Jest (unit), Playwright (E2E)
|
||||
- **Database:** PostgreSQL with migrations
|
||||
- **Deployment:** Docker Compose (local), staging via Traefik
|
||||
PM runs every 30 minutes autonomously. No human approval needed unless blocked.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-03-02
|
||||
**Version:** 1.0
|
||||
**Audience:** All Claude agents, PM, developers
|
||||
|
||||
> **Remember:** This document is your north star. Follow it. Extend it. Improve it.
|
||||
|
||||
---
|
||||
|
||||
## Part 5: Git Hygiene - Keep the Repo Clean
|
||||
|
||||
### No "Fix" Commits
|
||||
❌ **NEVER** commit fixes for typos, syntax errors, or accidentally added files:
|
||||
```bash
|
||||
# BAD - Creates noise in history:
|
||||
commit 1a2b3c: feat: add feature
|
||||
commit 1a2b3d: fix: typo in feature ← NO!
|
||||
commit 1a2b3e: chore: remove extra file ← NO!
|
||||
```
|
||||
|
||||
✅ **INSTEAD:** Use interactive rebase to clean up BEFORE pushing:
|
||||
```bash
|
||||
# GOOD - Clean, single commit:
|
||||
commit 1a2b3c: feat: add feature (with typo fixed)
|
||||
```
|
||||
|
||||
### Interactive Rebase Workflow
|
||||
|
||||
**1. Check recent commits:**
|
||||
```bash
|
||||
git log --oneline -5
|
||||
# abc1234 feat: add feature
|
||||
# def5678 fix: typo in feature ← Oops!
|
||||
# ghi9012 chore: remove extra file ← Oops!
|
||||
```
|
||||
|
||||
**2. Rebase the last 3 commits:**
|
||||
```bash
|
||||
git rebase -i HEAD~3
|
||||
```
|
||||
|
||||
**3. Edit in your editor:**
|
||||
```
|
||||
pick abc1234 feat: add feature
|
||||
squash def5678 fix: typo in feature
|
||||
squash ghi9012 chore: remove extra file
|
||||
```
|
||||
|
||||
Change `pick` to `squash` (or `s`) for commits you want to merge into the previous one.
|
||||
|
||||
**4. Save and edit the commit message:**
|
||||
The editor will ask for a final commit message. Edit it to be clean:
|
||||
```
|
||||
feat: add feature
|
||||
|
||||
- Added feature X
|
||||
- Removed extra file
|
||||
```
|
||||
|
||||
**5. Force-push (only if not yet pushed):**
|
||||
```bash
|
||||
git push -f origin branch-name
|
||||
```
|
||||
|
||||
### When to Rebase
|
||||
|
||||
- ✅ **Before first push** — Fix typos, add forgotten files
|
||||
- ✅ **Before review** — Squash "work in progress" commits
|
||||
- ❌ **After pushed to shared branch** — Don't force-push to main/develop
|
||||
|
||||
### Common Rebase Scenarios
|
||||
|
||||
**Fix a typo in last commit:**
|
||||
```bash
|
||||
git commit --amend
|
||||
# Edit the file, save
|
||||
# Git updates the commit without a new one
|
||||
```
|
||||
|
||||
**Reorder commits:**
|
||||
```bash
|
||||
git rebase -i HEAD~3
|
||||
# Reorder the lines in the editor
|
||||
```
|
||||
|
||||
**Combine last 2 commits:**
|
||||
```bash
|
||||
git rebase -i HEAD~2
|
||||
# Change second line from "pick" to "squash"
|
||||
```
|
||||
|
||||
### Rule of Thumb
|
||||
|
||||
**One logical change = One commit**
|
||||
|
||||
If you're writing a commit message that says "fixed typo from previous commit", you should have rebased instead.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Part 6: Branching Strategy - One Feature Per Branch
|
||||
|
||||
### The Workflow
|
||||
|
||||
**Each phase/feature gets its own branch:**
|
||||
|
||||
```
|
||||
main
|
||||
└─ feature/03-design-polish (Phase 03)
|
||||
└─ feature/04-workout-modification (Phase 04, branched from 03)
|
||||
└─ feature/05-exercise-encyclopedia (Phase 05, branched from 04)
|
||||
```
|
||||
|
||||
### Step-by-Step
|
||||
|
||||
#### 1. Work on Phase 03
|
||||
```bash
|
||||
git checkout -b feature/03-design-polish
|
||||
# ... do all Phase 03 work ...
|
||||
# Multiple commits, testing, refinement
|
||||
git push origin feature/03-design-polish
|
||||
```
|
||||
|
||||
#### 2. When Phase 03 is Done → Create PR
|
||||
```bash
|
||||
# Create PR: feature/03-design-polish → main
|
||||
# Get code review, merge when approved
|
||||
# This becomes the baseline for Phase 04
|
||||
```
|
||||
|
||||
#### 3. Start Phase 04 (NEW BRANCH from Phase 03)
|
||||
```bash
|
||||
# First, make sure local is up-to-date
|
||||
git checkout feature/03-design-polish
|
||||
git pull origin feature/03-design-polish
|
||||
|
||||
# Create new branch FROM the current feature
|
||||
git checkout -b feature/04-workout-modification
|
||||
|
||||
# Now do all Phase 04 work on this new branch
|
||||
# ... commits for 04-01, 04-02, 04-03, etc ...
|
||||
git push origin feature/04-workout-modification
|
||||
```
|
||||
|
||||
#### 4. When Phase 04 is Done → Rebase Before PR
|
||||
```bash
|
||||
# Make sure feature/03 is merged to main
|
||||
git fetch origin
|
||||
|
||||
# Rebase Phase 04 onto main (to get clean history)
|
||||
git rebase origin/main feature/04-workout-modification
|
||||
|
||||
# Force-push (since we're rebasing)
|
||||
git push -f origin feature/04-workout-modification
|
||||
|
||||
# Create PR: feature/04-workout-modification → main
|
||||
```
|
||||
|
||||
### Why This Matters
|
||||
|
||||
- ✅ **Cleaner history** — Each phase is separate
|
||||
- ✅ **Easier reviews** — PRs are focused on one phase
|
||||
- ✅ **Better testing** — Each phase tested independently
|
||||
- ✅ **Easy rollback** — Remove one phase without touching others
|
||||
- ✅ **Work in parallel** — Different agents can work on different phases
|
||||
|
||||
### Current Situation
|
||||
|
||||
We've mixed Phase 03 and 04 in `feature/03-design-polish`. Going forward:
|
||||
|
||||
1. **Merge feature/03-design-polish → main** (code review, then merge)
|
||||
2. **Create feature/04-workout-modification** from main
|
||||
3. Move/cherry-pick Phase 04 commits to new branch (or just continue from here)
|
||||
4. **Create feature/05-exercise-encyclopedia** from feature/04 when 04 is done
|
||||
|
||||
### Rebase Chain Example
|
||||
|
||||
```bash
|
||||
# After 03 is merged and 04 starts:
|
||||
git checkout feature/04-workout-modification
|
||||
git rebase origin/main # Rebase 04 onto latest main
|
||||
|
||||
# After 04 is merged and 05 starts:
|
||||
git checkout feature/05-exercise-encyclopedia
|
||||
git rebase origin/main # Always rebase new features onto main
|
||||
```
|
||||
|
||||
### Rule of Thumb
|
||||
|
||||
- **One feature per branch**
|
||||
- **Each branch is independent**
|
||||
- **Rebase onto main before PR**
|
||||
- **No "feature/03-design-polish" commits after it's merged**
|
||||
|
||||
**For questions:** Check specific agent SOUL.md or skill SKILL.md files
|
||||
|
||||
@@ -394,7 +394,7 @@ app.get('/api/today/:programId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Gravl API running on port ${PORT}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# Gravl Coding Conventions
|
||||
|
||||
## Utvecklingsmetodik
|
||||
|
||||
### Red/Green TDD (OBLIGATORISKT)
|
||||
|
||||
All ny kod måste följa TDD-cykeln:
|
||||
|
||||
```
|
||||
🔴 RED → 🟢 GREEN → 🔄 REFACTOR
|
||||
```
|
||||
|
||||
#### 1. 🔴 RED - Skriv test först
|
||||
```javascript
|
||||
// test/feature.test.js
|
||||
describe('Feature', () => {
|
||||
it('should do expected behavior', async () => {
|
||||
const result = await feature.doSomething();
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Kör testet - det MÅSTE faila!**
|
||||
```bash
|
||||
npm test -- --grep "Feature"
|
||||
# ❌ FAIL (detta är rätt!)
|
||||
```
|
||||
|
||||
#### 2. 🟢 GREEN - Minimal implementation
|
||||
Skriv bara tillräckligt med kod för att testet passerar:
|
||||
```javascript
|
||||
// src/feature.js
|
||||
export function doSomething() {
|
||||
return expected; // Minimal lösning
|
||||
}
|
||||
```
|
||||
|
||||
**Kör testet igen:**
|
||||
```bash
|
||||
npm test -- --grep "Feature"
|
||||
# ✅ PASS
|
||||
```
|
||||
|
||||
#### 3. 🔄 REFACTOR - Förbättra
|
||||
Nu kan du:
|
||||
- Refaktorera för clean code
|
||||
- Extrahera funktioner
|
||||
- Förbättra namngivning
|
||||
- Ta bort duplicering
|
||||
|
||||
**Kör testerna kontinuerligt:**
|
||||
```bash
|
||||
npm test
|
||||
# ✅ Alla test måste fortfarande passa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Teststruktur
|
||||
|
||||
```
|
||||
/workspace/gravl/
|
||||
├── src/
|
||||
│ └── components/
|
||||
├── server/
|
||||
│ └── routes/
|
||||
└── test/
|
||||
├── unit/ # Enhetstester
|
||||
├── integration/ # API-tester
|
||||
└── e2e/ # End-to-end
|
||||
```
|
||||
|
||||
## Namnkonventioner
|
||||
|
||||
### Tester
|
||||
- `[feature].test.js` - Unit tests
|
||||
- `[feature].integration.test.js` - Integration tests
|
||||
- Describe-block: Noun (vad testas)
|
||||
- It-block: "should [verb] [expected outcome]"
|
||||
|
||||
### Commits
|
||||
```
|
||||
test: add failing test for [feature]
|
||||
feat: implement [feature] to pass tests
|
||||
refactor: clean up [feature] implementation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow för kodningsagenter
|
||||
|
||||
1. **Få uppgift** från Gravl PM
|
||||
2. **Läs spec** i docs/current-task.md
|
||||
3. **Skriv failing test** - visa PM
|
||||
4. **Implementera** tills test passerar
|
||||
5. **Refaktorera** om nödvändigt
|
||||
6. **Commit** med rätt prefix
|
||||
7. **Rapportera** till PM
|
||||
|
||||
---
|
||||
|
||||
*Uppdaterad: 2026-02-28*
|
||||
-1
File diff suppressed because one or more lines are too long
+67
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -11,8 +11,8 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<title>Gravl - Träning</title>
|
||||
<script type="module" crossorigin src="/assets/index-DIGPkDnZ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D0xrERyI.css">
|
||||
<script type="module" crossorigin src="/assets/index-hhKetRGz.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-my_lGtI5.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -267,24 +267,6 @@ export const Icons = {
|
||||
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/>
|
||||
</svg>
|
||||
),
|
||||
spinner: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 2a10 10 0 0 1 10 10" opacity="0.3"/>
|
||||
</svg>
|
||||
),
|
||||
alert: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3.05h16.94a2 2 0 0 0 1.71-3.05L13.71 3.86a2 2 0 0 0-3.42 0z"/>
|
||||
<line x1="12" y1="9" x2="12" y2="13"/>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
||||
</svg>
|
||||
),
|
||||
checkmark: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
),
|
||||
}
|
||||
|
||||
// Icon component wrapper
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
$(cat Icons.jsx | sed '/^ refresh: (/,/^ ),$/a\
|
||||
\ spinner: (\
|
||||
\ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">\
|
||||
\ <circle cx="12" cy="12" r="10"/>\
|
||||
\ <path d="M12 2a10 10 0 0 1 10 10" opacity="0.3"/>\
|
||||
\ </svg>\
|
||||
\ ),\
|
||||
\ alert: (\
|
||||
\ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">\
|
||||
\ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3.05h16.94a2 2 0 0 0 1.71-3.05L13.71 3.86a2 2 0 0 0-3.42 0z"/>\
|
||||
\ <line x1="12" y1="9" x2="12" y2="13"/>\
|
||||
\ <line x1="12" y1="17" x2="12.01" y2="17"/>\
|
||||
\ </svg>\
|
||||
\ ),\
|
||||
\ checkmark: (\
|
||||
\ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">\
|
||||
\ <polyline points="20 6 9 17 4 12"/>\
|
||||
\ </svg>\
|
||||
\ ),')</EOFTEMP
|
||||
@@ -405,98 +405,6 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
/* Sync Status Indicator - Saving State */
|
||||
.sync-status.saving {
|
||||
background: #cfe8fc;
|
||||
color: #004085;
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spinner animation for saving state */
|
||||
.icon-spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* Save Timeout Warning Banner */
|
||||
.timeout-banner {
|
||||
background: #fff3cd;
|
||||
border-bottom: 1px solid #ffeaa7;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
color: #856404;
|
||||
font-size: 0.95rem;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Toast Notification Container */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-weight: 500;
|
||||
z-index: 2000;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
max-width: 90vw;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateX(-50%) translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile adjustments for toast */
|
||||
@media (max-width: 600px) {
|
||||
.toast {
|
||||
bottom: 2rem;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
transform: none;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.timeout-banner {
|
||||
font-size: 0.9rem;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Mobile/Tablet Adjustments */
|
||||
@media (max-width: 600px) {
|
||||
.page-header {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Icon } from '../components/Icons'
|
||||
import ExercisePicker from '../components/ExercisePicker'
|
||||
import { useDraftWorkout } from '../hooks/useDraftWorkout'
|
||||
@@ -17,37 +17,7 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
const [retryCount, setRetryCount] = useState(0)
|
||||
const [lastSavePayload, setLastSavePayload] = useState(null)
|
||||
|
||||
// Timeout and toast state
|
||||
const [saveTimeout, setSaveTimeout] = useState(false)
|
||||
const [toast, setToast] = useState(null) // { type, message }
|
||||
const saveStartTimeRef = useRef(null)
|
||||
const saveTimeoutRef = useRef(null)
|
||||
const toastTimeoutRef = useRef(null)
|
||||
|
||||
|
||||
// Cleanup timeouts on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
|
||||
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Show and auto-hide toast notifications
|
||||
useEffect(() => {
|
||||
if (!toast) return
|
||||
|
||||
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current)
|
||||
toastTimeoutRef.current = setTimeout(() => {
|
||||
setToast(null)
|
||||
}, 3000)
|
||||
|
||||
return () => {
|
||||
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current)
|
||||
}
|
||||
}, [toast])
|
||||
|
||||
// Show draft recovery prompt on first render
|
||||
// Show draft recovery prompt on first render
|
||||
const handleRecoverDraft = () => {
|
||||
if (hasDraft && !draftPromptShown) {
|
||||
setDraftPromptShown(true)
|
||||
@@ -137,19 +107,8 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
setSaving(true)
|
||||
setSyncStatus('saving')
|
||||
setError(null)
|
||||
setSaveTimeout(false)
|
||||
setToast(null)
|
||||
setRetryCount(prev => prev + 1)
|
||||
|
||||
// Track save start time for timeout warning
|
||||
saveStartTimeRef.current = Date.now()
|
||||
|
||||
// Set timeout warning at 8 seconds
|
||||
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
|
||||
saveTimeoutRef.current = setTimeout(() => {
|
||||
setSaveTimeout(true)
|
||||
}, 8000)
|
||||
|
||||
try {
|
||||
// Format for API
|
||||
const payload = {
|
||||
@@ -167,41 +126,28 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
// Call the save callback
|
||||
await onSave(workout.id, payload)
|
||||
|
||||
// Clear timeout warning
|
||||
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
|
||||
setSaveTimeout(false)
|
||||
|
||||
// Success: clear draft and show confirmation
|
||||
clearDraft()
|
||||
setSyncStatus('saved')
|
||||
setRetryCount(0) // Reset retry count on success
|
||||
|
||||
// Show success toast
|
||||
setToast({ type: 'success', message: 'Sparat!' })
|
||||
|
||||
// Log success
|
||||
console.log('Workout saved successfully', {
|
||||
workoutId: workout.id,
|
||||
exerciseCount: exercises.length,
|
||||
retryCount,
|
||||
duration: Date.now() - saveStartTimeRef.current
|
||||
retryCount
|
||||
})
|
||||
|
||||
// Reset status after 2 seconds
|
||||
setTimeout(() => setSyncStatus('idle'), 2000)
|
||||
} catch (err) {
|
||||
// Clear timeout warning
|
||||
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
|
||||
setSaveTimeout(false)
|
||||
|
||||
// Log error with context for debugging
|
||||
console.error('Failed to save workout:', {
|
||||
error: err,
|
||||
workoutId: workout.id,
|
||||
exerciseCount: exercises.length,
|
||||
retryCount,
|
||||
payload: lastSavePayload,
|
||||
duration: Date.now() - saveStartTimeRef.current
|
||||
payload: lastSavePayload
|
||||
})
|
||||
|
||||
// Determine error message based on error type
|
||||
@@ -209,9 +155,6 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
setError(errorMessage)
|
||||
setSyncStatus('error')
|
||||
|
||||
// Show error toast
|
||||
setToast({ type: 'error', message: 'Fel vid sparning' })
|
||||
|
||||
// Keep draft on error so user doesn't lose work
|
||||
// (useDraftWorkout already auto-saves, so no action needed here)
|
||||
} finally {
|
||||
@@ -273,11 +216,6 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
</button>
|
||||
<h1>Redigera pass</h1>
|
||||
<div className="save-header-group">
|
||||
{syncStatus === 'saving' && (
|
||||
<span className="sync-status saving">
|
||||
<Icon name="spinner" size={16} className="icon-spinner" /> Sparar...
|
||||
</span>
|
||||
)}
|
||||
{syncStatus === 'saved' && (
|
||||
<span className="sync-status saved">
|
||||
<Icon name="checkmark" size={16} /> Sparat
|
||||
@@ -292,9 +230,13 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
className="save-header-btn"
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
title={saving ? 'Sparar...' : 'Spara ändringar'}
|
||||
>
|
||||
{syncStatus === 'saving' ? 'Sparar...' : 'Spara'}
|
||||
{syncStatus === 'saving' && (
|
||||
<>
|
||||
<Icon name="spinner" size={16} /> Sparar...
|
||||
</>
|
||||
)}
|
||||
{syncStatus !== 'saving' && 'Spara'}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -321,23 +263,6 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Save Timeout Warning */}
|
||||
{saveTimeout && (
|
||||
<div className="timeout-banner">
|
||||
<Icon name="alert" size={18} />
|
||||
<span>Sparningen tar längre än vanligt. Kontrollera din anslutning.</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Toast Notifications */}
|
||||
{toast && (
|
||||
<div className={`toast toast-${toast.type}`}>
|
||||
{toast.type === 'success' && <Icon name="checkmark" size={16} />}
|
||||
{toast.type === 'error' && <Icon name="alert" size={16} />}
|
||||
<span>{toast.message}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<main className="edit-main">
|
||||
<div className="workout-meta-card">
|
||||
<h2>{workout.name}</h2>
|
||||
|
||||
Reference in New Issue
Block a user