19 Commits

Author SHA1 Message Date
clawd bca4057d14 docs: add branching strategy - one feature per branch
- Each phase gets its own branch
- Branch from previous phase to inherit work
- Rebase onto main before creating PR
- Keeps history clean and reviews focused
2026-03-02 08:56:32 +01:00
clawd d3ccbf5c8c docs: add git hygiene guidelines to CLAUDE.md
- No 'fix' commits for typos/syntax errors
- Use interactive rebase to keep history clean
- Examples: squash, amend, reorder commits
- Keep one logical change per commit
2026-03-02 08:52:40 +01:00
clawd d35cbeac5b docs: remove CODING-CONVENTIONS.md (content merged into CLAUDE.md)
All coding conventions, TDD patterns, and development guidelines
are now consolidated in CLAUDE.md
2026-03-02 08:49:18 +01:00
clawd 6b05b88bf1 docs: integrate coding conventions into CLAUDE.md
- Merged CODING-CONVENTIONS.md content into single authoritative document
- Part 1: Agent development principles (autonomy, verification, PM pattern)
- Part 2: Coding conventions (Red/Green/Refactor TDD, naming, commit patterns)
- Part 3: Operations (cron jobs, repo structure)
- Part 4: Agent/skill development workflow
- CLAUDE.md is now the single source of truth for agent development
2026-03-02 08:47:30 +01:00
clawd f4df4f7490 docs: add CLAUDE.md — agent development guidelines
- Core principles for autonomous agents with verification
- Checkpoint-based self-monitoring patterns
- Generalized agent workflow (no project-specific agents)
- Single source of truth in ~/clawd/claude-agents-skills/
- PM autonomy and cron job configuration
- Verification protocol to prevent hallucinations
- Together with CODING-CONVENTIONS.md, foundation for agent development
2026-03-02 08:46:26 +01:00
clawd 86f2baef44 chore: remove planning files from working directory 2026-03-02 08:42:03 +01:00
clawd 34a0c3b60d chore: exclude planning docs from git (kept locally) 2026-03-02 08:42:00 +01:00
clawd 219a52e0f2 chore: remove all node_modules directories from repo 2026-03-02 08:22:31 +01:00
clawd 120a629eea chore: add .gitignore and clean up repository
- Added comprehensive .gitignore for node_modules, dist, env files
- Removed node_modules from git history (keep locally)
- Removed empty/placeholder files (EOF, PLANEOF, create-staging.py)
- Repository now clean for public sharing
2026-03-02 08:17:06 +01:00
clawd d737d39f50 04-06-02: Save error handling & retry logic
- Added specific error type differentiation:
  * Network errors → 'Anslutning misslyckades'
  * Validation (400) → 'Ogiltiga ändringar'
  * Auth (401/403) → 'Saknar behörighet'
  * Server (500+) → 'Serverfel'
  * Generic fallback messages

- Implemented retry tracking:
  * retryCount state for monitoring attempts
  * lastSavePayload storage for potential retry (future feature)
  * Console logging with context for debugging

- Enhanced error handling:
  * getErrorMessage() function for error classification
  * Comprehensive error logging with workout/exercise context
  * Draft preserved on all error types (no data loss)

- Improved UI/UX:
  * Error banner with specific, actionable messages
  * 'Försök igen' button with retry tracking
  * Sync status feedback (idle/saving/saved/error)
  * Success checkmark animation (2s duration)
  * Spinner animation during save

- CSS Enhancements:
  * @keyframes spin for loading spinner
  * @keyframes slideInCheckmark for success feedback
  * Mobile-responsive error banner (flex column on <480px)
  * Smooth animations for state transitions

Tests: npm run build ✓ (no syntax errors)
Files modified:
  - frontend/src/pages/WorkoutEditPage.jsx
  - frontend/src/pages/WorkoutEditPage.css
2026-03-02 01:54:04 +01:00
clawd cac31e85bb 04-06: Plan persistence improvements and implement draft persistence
- Created 04-06-PLAN.md outlining persistence improvements phases
- Phase 04-06-01: Draft persistence via localStorage
  - Added useDraftWorkout hook for auto-saving/loading drafts
  - Integrated hook into WorkoutEditPage
  - Added draft recovery prompt UI
  - Drafts cleared after successful save
- Phase 04-06-02: Save error handling & retry (scaffolding)
  - Added error state and syncStatus tracking
  - Added handleRetry() for failed saves
  - Error banner with retry button
- Phase 04-06-03: Sync status UI (scaffolding)
  - Added visual feedback for save progress
  - Status indicators: saving, saved, error
  - Disabled UI during save to prevent conflicts
- Created comprehensive styles for new UI components

Status: 04-06-01 complete and integrated. Ready for testing.
2026-03-02 00:51:11 +01:00
clawd cf009ad975 04-05: Reset to Original feature - custom workouts can be reverted to program versions
- Added reset button (refresh icon) to custom workout cards
- Implemented confirmation dialog to prevent accidental resets
- Integrated with DELETE /api/custom-workouts/:id endpoint
- Added CSS styling: reset button, success message, modal dialog
- Added refresh icon to SVG library
- Frontend build successful

Changes:
- frontend/src/pages/WorkoutSelectPage.jsx (reset flow logic)
- frontend/src/App.css (170 new lines for reset/modal styling)
- frontend/src/components/Icons.jsx (refresh icon)
- Checkpoint updated with task completion metadata
2026-03-01 20:44:45 +01:00
clawd a2a3949269 feat(04-04-visual-distinction): Add custom vs program workout badges on WorkoutSelectPage
- Fetch custom workouts for authenticated user
- Display 'Anpassad' (custom) or 'Program' badge on each workout card
- Add badge component with orange accent for custom, muted color for program
- Badge positioned bottom-right of workout icon
- Responsive styling consistent with Gravl dark theme
- All build checks pass
2026-03-01 19:41:54 +01:00
clawd 81e0caab42 feat(04-03-partial): ExercisePicker and WorkoutEditPage components - swap/add/remove exercises with sets/reps editing 2026-03-01 15:36:47 +01:00
clawd e923a17707 test(e2e): add Playwright with browser tests for login, logo, dashboard 2026-03-01 09:15:54 +01:00
clawd 0ebcf54e09 feat(phase-4): Backend API for custom workouts
- Add custom_workouts and custom_workout_exercises tables (schema)
- New endpoints:
  - GET /api/exercises - List all exercises for picker
  - POST /api/custom-workouts - Fork program workout
  - GET /api/custom-workouts - List user's custom workouts
  - GET /api/custom-workouts/:id - Get workout with exercises
  - PUT /api/custom-workouts/:id - Update workout exercises
  - DELETE /api/custom-workouts/:id - Delete custom workout
- Updated endpoints for source_type support:
  - GET /api/logs - Filter by source_type and custom_workout_id
  - POST /api/logs - Save with source_type and custom_workout_id
  - DELETE /api/logs - Support custom workout log deletion
- Adds Phase 4 planning overview

Completes: 04-01-schema-migration, 04-02-backend-api
Next: 04-03-frontend-workout-edit
2026-03-01 03:36:53 +01:00
clawd 5229d793a9 fix(staging): fix Traefik service linking with explicit service labels 2026-03-01 00:23:52 +01:00
clawd b15497012c feat(staging): add Traefik-based staging with automatic subdomains 2026-03-01 00:14:22 +01:00
clawd b413d1755b feat(infra): add staging environment setup with docker-compose and scripts 2026-03-01 00:10:58 +01:00
15 changed files with 784 additions and 197 deletions
+54
View File
@@ -0,0 +1,54 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build & dist
dist/
build/
*.bundle.js
*.bundle.css
# Environment
.env
.env.local
.env.*.local
.env.production.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# OS
Thumbs.db
.DS_Store
# Logs
*.log
logs/
# Test coverage
.coverage/
coverage/
# Python
*.pyc
__pycache__/
*.py~
# Staging
/tmp/
/staging-*/
# Planning & Documentation (kept locally, not in repo)
.planning/
TODO.md
./frontend/.planning/
./frontend/tasks/
./docs/plans/
.claude/settings.local.json
+16 -16
View File
@@ -1,20 +1,20 @@
{
"lastRun": "2026-03-01T20:42:00+01:00",
"status": "completed",
"lastRun": "2026-03-02T03:55:00Z",
"status": "blocked",
"phase": "04-workout-modification",
"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"
"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"
],
"buildStatus": "success",
"buildTime": "3.59s"
"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"
}
+499
View File
@@ -0,0 +1,499 @@
# CLAUDE.md — Agent Development Foundation
This document is **THE** foundation for developing Claude agents and autonomous systems in Gravl.
Together with the actual codebase, this is your north star.
## Part 1: Agent Development Principles
### 1. Autonomy with Verification
- Agents execute tasks independently
- **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
### 2. Checkpoint-Based Self-Monitoring
All long-running tasks use checkpoint files:
```json
{
"lastRun": "2026-03-02T08:00:00Z",
"status": "completed|blocked|interrupted|error",
"result": "Summary of work",
"nextCheck": "What to do next"
}
```
**Recovery logic:**
- If `lastRun > 60min` OR `status ≠ "completed"` → trigger recovery
- Log recovery attempts to help debugging
- Use simple JSON for checkpoint files (no complex parsing)
### 3. PM (Project Manager) Autonomy
The Gravl PM agent:
- Plans sprints/phases autonomously
- Spawns specialized agents (frontend-dev, backend-dev, etc.)
- 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.**
Use generalized agents instead:
- `frontend-dev` — React/CSS specialist
- `backend-dev` — Node.js/PostgreSQL specialist
- `architect` — System design
- `reviewer` — Code review + quality gates
- `browser-tester` — E2E testing + QA
These live in `~/clawd/claude-agents-skills/agents/` and symlink to `~/clawd/agents/`.
### 5. Single Source of Truth
All skills and agents 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
### 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
### 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`
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`
5. Commit to hub repo
### Verification Pattern (CRITICAL)
After any subagent completes work:
```bash
# 1. Check git status
git status
# 2. Verify files changed
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
```
**This prevents hallucination bugs** where agents claim work they didn't do.
---
## 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
---
## PM Agent Playbook (30-minute cycles)
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
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
---
**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**
View File
View File
View File
-103
View File
@@ -1,103 +0,0 @@
# 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*
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<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-kl2SjtTw.js"></script>
<script type="module" crossorigin src="/assets/index-DIGPkDnZ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D0xrERyI.css">
</head>
<body>
+18
View File
@@ -267,6 +267,24 @@ 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
+19
View File
@@ -0,0 +1,19 @@
$(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
+92
View File
@@ -405,6 +405,98 @@
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 {
+85 -10
View File
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, useRef, useEffect } from 'react'
import { Icon } from '../components/Icons'
import ExercisePicker from '../components/ExercisePicker'
import { useDraftWorkout } from '../hooks/useDraftWorkout'
@@ -17,7 +17,37 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
const [retryCount, setRetryCount] = useState(0)
const [lastSavePayload, setLastSavePayload] = useState(null)
// Show draft recovery prompt on first render
// 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
const handleRecoverDraft = () => {
if (hasDraft && !draftPromptShown) {
setDraftPromptShown(true)
@@ -107,8 +137,19 @@ 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 = {
@@ -126,28 +167,41 @@ 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
retryCount,
duration: Date.now() - saveStartTimeRef.current
})
// 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
payload: lastSavePayload,
duration: Date.now() - saveStartTimeRef.current
})
// Determine error message based on error type
@@ -155,6 +209,9 @@ 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 {
@@ -216,6 +273,11 @@ 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
@@ -230,13 +292,9 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
className="save-header-btn"
onClick={handleSave}
disabled={saving}
title={saving ? 'Sparar...' : 'Spara ändringar'}
>
{syncStatus === 'saving' && (
<>
<Icon name="spinner" size={16} /> Sparar...
</>
)}
{syncStatus !== 'saving' && 'Spara'}
{syncStatus === 'saving' ? 'Sparar...' : 'Spara'}
</button>
</div>
</header>
@@ -263,6 +321,23 @@ 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>
View File